diff --git a/django/db/models/base.py b/django/db/models/base.py index d39c0b9ea6..dd2ac1de8c 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1553,8 +1553,8 @@ class Model(metaclass=ModelBase): errors = [] fields = cls._meta.ordering - # Skip '?' fields. - fields = (f for f in fields if f != '?') + # Skip expressions and '?' fields. + fields = (f for f in fields if isinstance(f, str) and f != '?') # Convert "-field" to "field". fields = ((f[1:] if f.startswith('-') else f) for f in fields) diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index bf8cf10c33..33ff581d39 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -256,9 +256,10 @@ Django quotes column and table names behind the scenes. ordering = ['-order_date'] - This is a tuple or list of strings. Each string is a field name with an optional - "-" prefix, which indicates descending order. Fields without a leading "-" will - be ordered ascending. Use the string "?" to order randomly. + This is a tuple or list of strings and/or query expressions. Each string is + a field name with an optional "-" prefix, which indicates descending order. + Fields without a leading "-" will be ordered ascending. Use the string "?" + to order randomly. For example, to order by a ``pub_date`` field ascending, use this:: @@ -272,9 +273,20 @@ Django quotes column and table names behind the scenes. ordering = ['-pub_date', 'author'] + You can also use :doc:`query expressions `. To + order by ``author`` ascending and make null values sort last, use this:: + + from django.db.models import F + + ordering = [F('author').asc(nulls_last=True)] + Default ordering also affects :ref:`aggregation queries `. + .. versionchanged:: 2.0 + + Support for query expressions was added. + .. warning:: Ordering is not a free operation. Each field you add to the ordering diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt index 4f92927c1e..a8530d11c2 100644 --- a/docs/releases/2.0.txt +++ b/docs/releases/2.0.txt @@ -283,6 +283,9 @@ Models different conditionals ` to multiple aggregations over the same fields or relations. +* Added support for expressions in :attr:`Meta.ordering + `. + Requests and Responses ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/ordering/models.py b/tests/ordering/models.py index d3024f5fc0..85e8c59bb6 100644 --- a/tests/ordering/models.py +++ b/tests/ordering/models.py @@ -42,6 +42,12 @@ class OrderedByAuthorArticle(Article): ordering = ('author', 'second_author') +class OrderedByFArticle(Article): + class Meta: + proxy = True + ordering = (models.F('author').asc(nulls_first=True), 'id') + + class Reference(models.Model): article = models.ForeignKey(OrderedByAuthorArticle, models.CASCADE) diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py index ff749331b9..dbc924b06b 100644 --- a/tests/ordering/tests.py +++ b/tests/ordering/tests.py @@ -5,7 +5,7 @@ from django.db.models import F from django.db.models.functions import Upper from django.test import TestCase -from .models import Article, Author, Reference +from .models import Article, Author, OrderedByFArticle, Reference class OrderingTests(TestCase): @@ -368,3 +368,13 @@ class OrderingTests(TestCase): r1 = Reference.objects.create(article_id=self.a1.pk) r2 = Reference.objects.create(article_id=self.a2.pk) self.assertSequenceEqual(Reference.objects.all(), [r2, r1]) + + def test_default_ordering_by_f_expression(self): + """F expressions can be used in Meta.ordering.""" + articles = OrderedByFArticle.objects.all() + articles.filter(headline='Article 2').update(author=self.author_2) + articles.filter(headline='Article 3').update(author=self.author_1) + self.assertQuerysetEqual( + articles, ['Article 1', 'Article 4', 'Article 3', 'Article 2'], + attrgetter('headline') + )