#37330 assigned Bug

Transform on an annotation alias crashes when output_field is not attached to a model

Reported by: Kunal Kumar Owned by:
Component: Uncategorized Version: dev
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Applying a transform to an annotation alias raises AttributeError when the
annotation's output_field is not attached to a model:

Book.objects.annotate(
    published=ExpressionWrapper(F("pubdate"), output_field=DateField())
).values("published__year")
AttributeError: 'DateField' object has no attribute 'model'

Value(..., output_field=DateField()) fails the same way.

The same query works when the annotation's output_field happens to be a model
field, so the outcome depends on how the annotation was spelled rather than on
what it asks for:

annotation .values("ayear")
annotate(a=F("pubdate")) works
annotate(a=ExpressionWrapper(F("pubdate"), output_field=DateField())) AttributeError
annotate(a=Value(date(2026, 1, 1), output_field=DateField())) AttributeError

Cause: names_to_path() resolves an annotation name to
self.annotations[name].output_field, which annotate() does not require to be
attached to a model. setup_joins()'s final_transformer() then calls
field.get_col(alias), and Field.get_col() dereferences self.model.

names_to_path() already anticipates this a few lines earlier:

try:
    model = field.model._meta.concrete_model
except AttributeError:
    # QuerySet.annotate() may introduce fields that aren't attached to a model.
    model = None

An AttributeError escaping here also bypasses add_fields(), which catches
FieldError to build a helpful message.

Closest precedent found: #34921, same class of failure (an unsuccessful
self.model access on an unbound field, there in DateTimeField.to_python()).
I also searched for "unbound annotation", "output_field model attribute" and
"has no attribute model"; #36787 and #37248 are the nearest hits and neither
covers this path — both are closed/fixed with different symptoms.

Patch: https://github.com/django/django/pull/21914

Change History (0)

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