Browse Source

Fixed #29538 -- Fixed crash of ordering by related fields when Meta.ordering contains expressions.

Thanks Simon Charette for the review.
Ed Rivas 2 years ago
parent
commit
2798c937de

+ 12 - 0
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]

+ 7 - 2
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)

+ 18 - 0
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)

+ 37 - 1
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],
+        )