Opened 3 weeks ago
Last modified 41 hours ago
#37330 assigned Bug
Transform on an annotation alias crashes when output_field is not attached to a model
| Reported by: | Kunal Kumar | Owned by: | Kunal Kumar |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Release blocker | Keywords: | |
| Cc: | Jacob Walls | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | yes | 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.
Change History (5)
comment:1 by , 3 weeks ago
| Component: | Uncategorized → Database layer (models, ORM) |
|---|---|
| Owner: | set to |
comment:2 by , 3 weeks ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:3 by , 3 weeks ago
| Patch needs improvement: | set |
|---|
comment:4 by , 4 days ago
| Cc: | added |
|---|---|
| Severity: | Normal → Release blocker |
The same query works when the annotation's output_field happens to be a model field
While it doesn't crash, the output seems incorrect to me.
Here is a test to add to tests/annotations/tests.py
@register_lookup(CharField, Length)
def test_values_transform_on_annotation_of_related_field(self):
# Book and Publisher both have a "name" column.
qs = Book.objects.annotate(publisher_name=F("publisher__name"))
self.assertEqual(
qs.values_list("publisher_name__length", flat=True).get(pk=self.b1.pk),
len(self.p1.name),
)
This fails with
AssertionError: 58 != 6
The query returned the length of the book name rather than the publisher name.
I bisected the change to be6cf832293779d8aeaeddca8c47a37ba1530898. Before this change it crashed with FieldError but now returns a value which I believe to be incorrect. I think the severity of this should be increased to a release blocker.
comment:5 by , 41 hours ago
| Needs tests: | set |
|---|---|
| Patch needs improvement: | unset |
Good catch, David. Kunal, can you add David's test case to your PR?
Replicated, thank you for the report