Opened 3 years ago

Closed 3 years ago

Last modified 3 years ago

#24508 closed Bug (fixed)

F() object operations do not correcly reflect with annotate

Reported by: mijamo Owned by: Josh Smeaton
Component: Database layer (models, ORM) Version: 1.8rc1
Severity: Release blocker Keywords: F()
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Hello,

This ticket is related to the 1.8 new feature of using various F operations within an annotation.

I have spotted 2 problems so far :
1) F('field') * 2 apparently isn't the same as 2 * F('field')
Description of the problem :

SomeModel.objects.all().annotate(computed = F('some_field') * 2)
Works as expected

SomeModel.objects.all().annotate(computed = 2 * F('some_field'))
E: django.core.exceptions.FieldError: Expression contains mixed types. You must set output_field

SomeModel.objects.all().annotate(computed = Expression(2 * F('some_field'), output_field = FloatField()))
E : TypeError: __init__() got multiple values for argument 'output_field' 

The last exception, about init, is the most problematic as it doesn't state anything about where is the problem (no info in the traceback)

2) When a F object is added to None, it causes a conflict
Description of the problem :

SomeModel.objects.all().annotate(computed = F('some_field') + None)
Works as expected

SomeModel.objects.all().annotate(computed = None + F('some_field'))
E: django.core.exceptions.FieldError: Expression contains mixed types. You must set output_field

SomeModel.objects.all().annotate(computed = Expression(None + F('some_field'), output_field = FloatField()))
E : TypeError: __init__() got multiple values for argument 'output_field' 

You might wonder why there would be a None in the construction. For me it is because I am building the object in a for, so I have something like this :

annotation = None
for field in fields_to_be_added:
    annotation += F(field)
SomeModel.objects.all().annotate(annotation)

Change History (5)

comment:1 Changed 3 years ago by mijamo

Summary: F() object operations correcly reflecting with annotateF() object operations do not correcly reflect with annotate

comment:2 Changed 3 years ago by Josh Smeaton

Part of this problem will be solved by https://github.com/django/django/pull/4349

SomeModel.objects.all().annotate(computed = ExpressionWrapper(None + F('some_field'), output_field = FloatField()))

Where ExpressionWrapper replaces your previous (and incorrectly documented) Expression.

The second part, where you get different results depending on which node is left or right could possibly be fixed, but we may not be able to if it results in backwards compatibility problems.

What is the field type of some_field in your example? An integer or float?

comment:3 Changed 3 years ago by Josh Smeaton

Has patch: set
Owner: changed from nobody to Josh Smeaton
Status: newassigned
Triage Stage: UnreviewedReady for checkin

Turned out this wasn't so difficult to fix. I've opened a PR https://github.com/django/django/pull/4352

I can't see there being in compatibility concerns. The behaviour is now inline with filter(a=2+F('b').

comment:4 Changed 3 years ago by Josh Smeaton <josh.smeaton@…>

Resolution: fixed
Status: assignedclosed

In 127b3873:

Fixed #24508 -- Made annotations commutative

comment:5 Changed 3 years ago by Josh Smeaton <josh.smeaton@…>

In 3a1886d1:

[1.8.x] Fixed #24508 -- Made annotations commutative

Backport of 127b3873d03704f77428b984de022664b268314e from master

Note: See TracTickets for help on using tickets.
Back to Top