Search through the doc page on expressions to see many examples: https://docs.djangoproject.com/en/3.2/ref/models/expressions/
- The
output_field
parameter is to resolve conflicting or ambiguous types in an expression, if Django does not have sufficient heuristics to determine by itself.
- Django offers the
output_field
as a route for you to tell it what output type you actually want.
- The
output_field
parameter is not so much to allow you to cast the type of your values, it's to help Django resolve the output type amongst competing options.
- The
output_field
parameter will not always be required, but sometimes it will resolve ambiguity which would otherwise cause Django to raise an exception.
Looking at your examples, I can only presume that Django disregards your output_field
in these cases.
With respect to the different output between the two, Django treats the bare string "id"
as a field name.
If you wrapped the "id"
string with Value("id")
like you did for "yes", you would see a return of "id"
instead.
And if you removed the Value
wrapper from your "yes"
example you would see: FieldError: Cannot resolve keyword 'yes' into field. Choices are: ...
As a more involved example to indicate how the output_field
parameter can be useful, consider MS SQL Server, which does not have a proper Boolean type, and uses bit fields for the purpose instead.
Attempting
queryset = queryset.annotate( foo=Case(When(name__icontains='XYZ', then=Value(True)), default=Value(False)) )
gives
django.core.exceptions.FieldError: Cannot resolve expression type, unknown output_field
Even though you've submitted two, unambiguously Boolean values, Django cannot find the right type to return.
This is what the output_field
is for. You can resolve the ambiguity for Django by providing the type you want to use:
queryset = queryset.annotate( foo=Case(When(name__icontains='XYZ', then=Value(True)), default=Value(False), output_field=BooleanField()) )
Apparently Django is happy to try that but can't complete it under MS SQL Server:
django.db.utils.ProgrammingError: ('42000', '[42000] [FreeTDS][SQL Server]Statement(s) could not be prepared. (8180) (SQLExecDirectW)')
So to really help Django out you need to know a bit [sic] more than Django and supply:
queryset = queryset.annotate( foo=Case(When(name__icontains='XYZ', then=Value(True)), default=Value(False), output_field=BinaryField()) )
(Works).
To apply this to your example: MyModel.objects.annotate(foo=Max(False, output_field=BooleanField())).first().foo
returns False
.
And even using a numeric value, Django is able to interpret the value according to your supplied output_field
: MyModel.objects.annotate(foo=Max(0, output_field=BooleanField())).first().foo
returns False
.
But as we saw, although Django can handle this, MS SQL Server will not tolerate a boolean value, because it doesn't have them; so you may find you need to do this in the particular case of MS SQL Server: MyModel.objects.annotate(foo=Max(0, output_field=BinaryField())).first().foo
which returns 0
.
So the output_field
parameter provides this additional control.