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

Thanks Simon Charette for the review.
This commit is contained in:
Ed Rivas 2022-05-04 18:10:53 -06:00 committed by Mariusz Felisiak
parent 34e2148fc7
commit 2798c937de
4 changed files with 74 additions and 3 deletions

View File

@ -402,6 +402,18 @@ class BaseExpression:
def copy(self): def copy(self):
return copy.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): def get_group_by_cols(self, alias=None):
if not self.contains_aggregate: if not self.contains_aggregate:
return [self] return [self]

View File

@ -912,10 +912,15 @@ class SQLCompiler:
): ):
item = item.desc() if descending else item.asc() item = item.desc() if descending else item.asc()
if isinstance(item, OrderBy): if isinstance(item, OrderBy):
results.append((item, False)) results.append(
(item.prefix_references(f"{name}{LOOKUP_SEP}"), False)
)
continue continue
results.extend( 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 return results
targets, alias, _ = self.query.trim_joins(targets, joins, path) targets, alias, _ = self.query.trim_joins(targets, joins, path)

View File

@ -62,3 +62,21 @@ class Reference(models.Model):
class Meta: class Meta:
ordering = ("article",) 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)

View File

@ -14,7 +14,16 @@ from django.db.models import (
from django.db.models.functions import Upper from django.db.models.functions import Upper
from django.test import TestCase 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): class OrderingTests(TestCase):
@ -550,3 +559,30 @@ class OrderingTests(TestCase):
{"author": self.author_2.pk, "count": 1}, {"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],
)