Reputation: 31
Django doc says for aggregate functions:
output_field - An optional argument that represents the model field of the return value
So I expected that the code below returns "True".
MyModel.objects.annotate(foo=Max(Value("yes"), output_field=BooleanField())).first().foo
or
MyModel.objects.annotate(foo=Max("id", output_field=BooleanField())).first().foo
But string "yes" is returned in the first case and some integer in the second one.
I know I can use "Cast", but can somebody tell me, what is "output_field" good for in this case?
Upvotes: 3
Views: 3163
Reputation: 4160
Search through the doc page on expressions to see many examples: https://docs.djangoproject.com/en/3.2/ref/models/expressions/
output_field
parameter is to resolve conflicting or ambiguous types in an expression, if Django does not have sufficient heuristics to determine by itself.output_field
as a route for you to tell it what output type you actually want.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.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.
Upvotes: 1
Reputation: 5611
In some cases Django needs to know the type of data in some field explicitly.
For example, in case you'll try to feed results of such queryset into some serializer (from Django REST Framework), you'll get an error and will be forced to provide output field.
Upvotes: 0