#36404 closed Bug (fixed)
Aggregates with filter using OuterRef raise FieldError
Reported by: | Adam Johnson | Owned by: | Adam Johnson |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 5.2 |
Severity: | Release blocker | Keywords: | |
Cc: | Simon Charette | 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 (last modified by )
While upgrading a client project to Django 5.2, I encountered some FieldError
s like:
FieldError: Cannot resolve keyword 'isbn' into field. Choices are: book, book_id, id, isbn_ref
The issue is reproduced in this example project, with models:
from django.db import models class Book(models.Model): isbn = models.IntegerField() class Chapter(models.Model): book = models.ForeignKey(Book, on_delete=models.CASCADE) isbn_ref = models.IntegerField()
And QuerySet
:
from django.db.models import Count, OuterRef, Q, Subquery from example.models import Book, Chapter qs = Book.objects.annotate( point_count=Subquery( Chapter.objects.annotate( count=Count( "id", filter=Q(isbn_ref=OuterRef("isbn")), ) ).values("count") ) )
Full traceback:
$ python t.py Traceback (most recent call last): File "/.../t.py", line 14, in <module> Chapter.objects.annotate( ~~~~~~~~~~~~~~~~~~~~~~~~^ count=Count( ^^^^^^^^^^^^ ...<2 lines>... ) ^ ).values("count") ^ File "/.../django/django/db/models/manager.py", line 87, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ File "/.../django/django/db/models/query.py", line 1643, in annotate return self._annotate(args, kwargs, select=True) ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../django/django/db/models/query.py", line 1695, in _annotate clone.query.add_annotation( ~~~~~~~~~~~~~~~~~~~~~~~~~~^ annotation, ^^^^^^^^^^^ alias, ^^^^^^ select=select, ^^^^^^^^^^^^^^ ) ^ File "/.../django/django/db/models/sql/query.py", line 1218, in add_annotation annotation = annotation.resolve_expression(self, allow_joins=True, reuse=None) File "/.../django/django/db/models/aggregates.py", line 271, in resolve_expression result = super().resolve_expression(*args, **kwargs) File "/.../django/django/db/models/aggregates.py", line 124, in resolve_expression c.filter.resolve_expression(query, allow_joins, reuse, summarize) ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../django/django/db/models/expressions.py", line 300, in resolve_expression expr.resolve_expression(query, allow_joins, reuse, summarize) ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../django/django/db/models/sql/where.py", line 288, in resolve_expression clone._resolve_node(clone, *args, **kwargs) ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../django/django/db/models/sql/where.py", line 280, in _resolve_node cls._resolve_node(child, query, *args, **kwargs) ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../django/django/db/models/sql/where.py", line 284, in _resolve_node node.rhs = cls._resolve_leaf(node.rhs, query, *args, **kwargs) ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../django/django/db/models/sql/where.py", line 273, in _resolve_leaf expr = expr.resolve_expression(query, *args, **kwargs) File "/.../django/django/db/models/expressions.py", line 958, in resolve_expression col = super().resolve_expression(*args, **kwargs) File "/.../django/django/db/models/expressions.py", line 902, in resolve_expression return query.resolve_ref(self.name, allow_joins, reuse, summarize) ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../django/django/db/models/sql/query.py", line 2049, in resolve_ref join_info = self.setup_joins( field_list, self.get_meta(), self.get_initial_alias(), can_reuse=reuse ) File "/.../django/django/db/models/sql/query.py", line 1900, in setup_joins path, final_field, targets, rest = self.names_to_path( ~~~~~~~~~~~~~~~~~~^ names[:pivot], ^^^^^^^^^^^^^^ ...<2 lines>... fail_on_missing=True, ^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/.../django/django/db/models/sql/query.py", line 1805, in names_to_path raise FieldError( ...<2 lines>... ) django.core.exceptions.FieldError: Cannot resolve keyword 'isbn' into field. Choices are: book, book_id, id, isbn_ref
The error occurs because the isbn
field is looked up against the inner model, Chapter
, rather than the outer one, Book
.
I bisected the error to e306687a3a5507d59365ba9bf545010e5fd4b2a8. That commit referenced #36042 and was part of a PR for #36117. The simplification left a duplicated call to resolve_expression()
for Aggregate.filter
, leading to “over resolution” and triggering the FieldError
. The issue is similar to #36117, which removed some duplicate resolve_expression()
methods from Case
and When
.
While fixing this issue, I spotted the same mistake had been made for the newly added Aggregate.order_by
from #35444, which the PR associated to this ticket fixes in a second commit. The first commit should be backported to 5.2, the second left only on main
.
Change History (7)
comment:1 by , 8 weeks ago
Description: | modified (diff) |
---|
comment:2 by , 8 weeks ago
Triage Stage: | Unreviewed → Accepted |
---|---|
Version: | dev → 5.2 |
comment:4 by , 8 weeks ago
Cc: | added |
---|
comment:5 by , 8 weeks ago
Triage Stage: | Accepted → Ready for checkin |
---|
Thank you I've replicated
For admin purposes, I think it might make sense to open a separate ticket for this as a release blocker for "dev" with a different regression commit etc
The one for 5.2 would require a release note