diff --git a/AUTHORS b/AUTHORS index cf78600873..d17070362b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1033,6 +1033,7 @@ answer newbie questions, and generally made Django that much better: Will Ayd William Schwartz Will Hardy + Will Zhao Wilson Miner Wim Glenn wojtek diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index ac3ddfbc9d..4ea179ecde 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -885,6 +885,7 @@ class ResolvedOuterRef(F): class OuterRef(F): contains_aggregate = False + contains_over_clause = False def resolve_expression(self, *args, **kwargs): if isinstance(self.name, self.__class__): diff --git a/docs/releases/4.2.5.txt b/docs/releases/4.2.5.txt index fe216cadb3..d88ece91e6 100644 --- a/docs/releases/4.2.5.txt +++ b/docs/releases/4.2.5.txt @@ -21,3 +21,6 @@ Bugfixes * Fixed a regression in Django 4.2.2 that caused an unnecessary selection of a non-nullable ``ManyToManyField`` without a natural key during serialization (:ticket:`34779`). + +* Fixed a regression in Django 4.2 that caused a crash of a queryset when + filtering against deeply nested ``OuterRef()`` annotations (:ticket:`34803`). diff --git a/tests/expressions/models.py b/tests/expressions/models.py index 35985dc5f0..0a8a0a6584 100644 --- a/tests/expressions/models.py +++ b/tests/expressions/models.py @@ -8,6 +8,9 @@ from django.db import models class Manager(models.Model): name = models.CharField(max_length=50) + secretary = models.ForeignKey( + "Employee", models.CASCADE, null=True, related_name="managers" + ) class Employee(models.Model): @@ -15,6 +18,7 @@ class Employee(models.Model): lastname = models.CharField(max_length=50) salary = models.IntegerField(blank=True, null=True) manager = models.ForeignKey(Manager, models.CASCADE, null=True) + based_in_eu = models.BooleanField(default=False) def __str__(self): return "%s %s" % (self.firstname, self.lastname) diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index 9ba354f9f4..4bd46762e1 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -870,6 +870,36 @@ class BasicExpressionsTests(TestCase): ).filter(ceo_company__isnull=False) self.assertEqual(qs.get().ceo_company, "Test GmbH") + def test_annotation_with_deeply_nested_outerref(self): + bob = Employee.objects.create(firstname="Bob", based_in_eu=True) + self.max.manager = Manager.objects.create(name="Rock", secretary=bob) + self.max.save() + qs = Employee.objects.filter( + Exists( + Manager.objects.filter( + Exists( + Employee.objects.filter( + pk=OuterRef("secretary__pk"), + ) + .annotate( + secretary_based_in_eu=OuterRef(OuterRef("based_in_eu")) + ) + .filter( + Exists( + Company.objects.filter( + # Inner OuterRef refers to an outer + # OuterRef (not ResolvedOuterRef). + based_in_eu=OuterRef("secretary_based_in_eu") + ) + ) + ) + ), + secretary__pk=OuterRef("pk"), + ) + ) + ) + self.assertEqual(qs.get(), bob) + def test_pickle_expression(self): expr = Value(1) expr.convert_value # populate cached property