From 14917c9ae272f47d23401100faa6cefa8e1728bf Mon Sep 17 00:00:00 2001 From: David Wobrock Date: Thu, 21 Dec 2023 23:20:36 +0100 Subject: [PATCH] Fixed #35050 -- Fixed prefixing field names in FilteredRelation(). Thanks Mark Zorn for the report. Regression in 59f475470494ce5b8cbff816b1e5dafcbd10a3a3. --- django/db/models/sql/query.py | 6 ++++-- docs/releases/5.0.1.txt | 3 +++ tests/filtered_relation/models.py | 2 ++ tests/filtered_relation/tests.py | 36 +++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 7166b8cb5d96..a79d66eb21e9 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -94,14 +94,16 @@ def get_child_with_renamed_prefix(prefix, replacement, child): return rename_prefix_from_q(prefix, replacement, child) if isinstance(child, tuple): lhs, rhs = child - lhs = lhs.replace(prefix, replacement, 1) + if lhs.startswith(prefix + LOOKUP_SEP): + lhs = lhs.replace(prefix, replacement, 1) if not isinstance(rhs, F) and hasattr(rhs, "resolve_expression"): rhs = get_child_with_renamed_prefix(prefix, replacement, rhs) return lhs, rhs if isinstance(child, F): child = child.copy() - child.name = child.name.replace(prefix, replacement, 1) + if child.name.startswith(prefix + LOOKUP_SEP): + child.name = child.name.replace(prefix, replacement, 1) elif hasattr(child, "resolve_expression"): child = child.copy() child.set_source_expressions( diff --git a/docs/releases/5.0.1.txt b/docs/releases/5.0.1.txt index 592454e89344..15b735b42ee1 100644 --- a/docs/releases/5.0.1.txt +++ b/docs/releases/5.0.1.txt @@ -29,3 +29,6 @@ Bugfixes overflow the page and become non-interactive (:ticket:`35012`). * Added compatibility for ``oracledb`` 2.0.0 (:ticket:`35054`). + +* Fixed a regression in Django 5.0 where querysets referenced incorrect field + names from ``FilteredRelation()`` (:ticket:`35050`). diff --git a/tests/filtered_relation/models.py b/tests/filtered_relation/models.py index d34a86305fcc..765d4956e2a3 100644 --- a/tests/filtered_relation/models.py +++ b/tests/filtered_relation/models.py @@ -36,6 +36,8 @@ class Book(models.Model): related_query_name="book", ) editor = models.ForeignKey(Editor, models.CASCADE) + number_editor = models.IntegerField(default=-1) + editor_number = models.IntegerField(default=-2) generic_author = GenericRelation(Author) state = models.CharField(max_length=9, choices=STATES, default=AVAILABLE) diff --git a/tests/filtered_relation/tests.py b/tests/filtered_relation/tests.py index 5a21a47f369f..19714e844331 100644 --- a/tests/filtered_relation/tests.py +++ b/tests/filtered_relation/tests.py @@ -792,6 +792,42 @@ def test_conditional_expression_with_multiple_fields(self): ).filter(my_books__isnull=True) self.assertSequenceEqual(qs, []) + def test_conditional_expression_rhs_contains_relation_name(self): + qs = Book.objects.annotate( + rel=FilteredRelation( + "editor", + condition=Q(id=1 * F("number_editor")), + ) + ).filter(rel__isnull=True) + self.assertSequenceEqual(qs, []) + + def test_conditional_expression_rhs_startswith_relation_name(self): + qs = Book.objects.annotate( + rel=FilteredRelation( + "editor", + condition=Q(id=1 * F("editor_number")), + ) + ).filter(rel__isnull=True) + self.assertSequenceEqual(qs, []) + + def test_conditional_expression_lhs_startswith_relation_name(self): + qs = Book.objects.annotate( + rel=FilteredRelation( + "editor", + condition=Q(editor_number__gt=1), + ) + ).filter(rel__isnull=True) + self.assertSequenceEqual(qs, []) + + def test_conditional_expression_lhs_contains_relation_name(self): + qs = Book.objects.annotate( + rel=FilteredRelation( + "editor", + condition=Q(number_editor__gt=1), + ) + ).filter(rel__isnull=True) + self.assertSequenceEqual(qs, []) + class FilteredRelationAggregationTests(TestCase): @classmethod