From 2798c937deb6625a4e6a36e70d4d60ce5faac954 Mon Sep 17 00:00:00 2001 From: Ed Rivas Date: Wed, 4 May 2022 18:10:53 -0600 Subject: [PATCH] Fixed #29538 -- Fixed crash of ordering by related fields when Meta.ordering contains expressions. Thanks Simon Charette for the review. --- django/db/models/expressions.py | 12 ++++++++++ django/db/models/sql/compiler.py | 9 ++++++-- tests/ordering/models.py | 18 +++++++++++++++ tests/ordering/tests.py | 38 +++++++++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 4bc55a1c89..148b19b462 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -402,6 +402,18 @@ class BaseExpression: def copy(self): return copy.copy(self) + def prefix_references(self, prefix): + clone = self.copy() + clone.set_source_expressions( + [ + F(f"{prefix}{expr.name}") + if isinstance(expr, F) + else expr.prefix_references(prefix) + for expr in self.get_source_expressions() + ] + ) + return clone + def get_group_by_cols(self, alias=None): if not self.contains_aggregate: return [self] diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 13b606255c..9c7bd8ea1a 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -912,10 +912,15 @@ class SQLCompiler: ): item = item.desc() if descending else item.asc() if isinstance(item, OrderBy): - results.append((item, False)) + results.append( + (item.prefix_references(f"{name}{LOOKUP_SEP}"), False) + ) continue results.extend( - self.find_ordering_name(item, opts, alias, order, already_seen) + (expr.prefix_references(f"{name}{LOOKUP_SEP}"), is_ref) + for expr, is_ref in self.find_ordering_name( + item, opts, alias, order, already_seen + ) ) return results targets, alias, _ = self.query.trim_joins(targets, joins, path) diff --git a/tests/ordering/models.py b/tests/ordering/models.py index fce8b9cd42..c365da7642 100644 --- a/tests/ordering/models.py +++ b/tests/ordering/models.py @@ -62,3 +62,21 @@ class Reference(models.Model): class Meta: ordering = ("article",) + + +class OrderedByExpression(models.Model): + name = models.CharField(max_length=30) + + class Meta: + ordering = [models.functions.Lower("name")] + + +class OrderedByExpressionChild(models.Model): + parent = models.ForeignKey(OrderedByExpression, models.CASCADE) + + class Meta: + ordering = ["parent"] + + +class OrderedByExpressionGrandChild(models.Model): + parent = models.ForeignKey(OrderedByExpressionChild, models.CASCADE) diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py index 37106aa5dd..e4f7d75992 100644 --- a/tests/ordering/tests.py +++ b/tests/ordering/tests.py @@ -14,7 +14,16 @@ from django.db.models import ( from django.db.models.functions import Upper from django.test import TestCase -from .models import Article, Author, ChildArticle, OrderedByFArticle, Reference +from .models import ( + Article, + Author, + ChildArticle, + OrderedByExpression, + OrderedByExpressionChild, + OrderedByExpressionGrandChild, + OrderedByFArticle, + Reference, +) class OrderingTests(TestCase): @@ -550,3 +559,30 @@ class OrderingTests(TestCase): {"author": self.author_2.pk, "count": 1}, ], ) + + def test_order_by_parent_fk_with_expression_in_default_ordering(self): + p3 = OrderedByExpression.objects.create(name="oBJ 3") + p2 = OrderedByExpression.objects.create(name="OBJ 2") + p1 = OrderedByExpression.objects.create(name="obj 1") + c3 = OrderedByExpressionChild.objects.create(parent=p3) + c2 = OrderedByExpressionChild.objects.create(parent=p2) + c1 = OrderedByExpressionChild.objects.create(parent=p1) + self.assertSequenceEqual( + OrderedByExpressionChild.objects.order_by("parent"), + [c1, c2, c3], + ) + + def test_order_by_grandparent_fk_with_expression_in_default_ordering(self): + p3 = OrderedByExpression.objects.create(name="oBJ 3") + p2 = OrderedByExpression.objects.create(name="OBJ 2") + p1 = OrderedByExpression.objects.create(name="obj 1") + c3 = OrderedByExpressionChild.objects.create(parent=p3) + c2 = OrderedByExpressionChild.objects.create(parent=p2) + c1 = OrderedByExpressionChild.objects.create(parent=p1) + g3 = OrderedByExpressionGrandChild.objects.create(parent=c3) + g2 = OrderedByExpressionGrandChild.objects.create(parent=c2) + g1 = OrderedByExpressionGrandChild.objects.create(parent=c1) + self.assertSequenceEqual( + OrderedByExpressionGrandChild.objects.order_by("parent"), + [g1, g2, g3], + )