Skip to content

Commit 68da6b3

Browse files
authored
Fixed #33543 -- Deprecated passing nulls_first/nulls_last=False to OrderBy and Expression.asc()/desc().
Thanks Allen Jonathan David for the initial patch.
1 parent 2798c93 commit 68da6b3

File tree

5 files changed

+70
-10
lines changed

5 files changed

+70
-10
lines changed

django/db/models/expressions.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import datetime
33
import functools
44
import inspect
5+
import warnings
56
from collections import defaultdict
67
from decimal import Decimal
78
from uuid import UUID
@@ -12,6 +13,7 @@
1213
from django.db.models.constants import LOOKUP_SEP
1314
from django.db.models.query_utils import Q
1415
from django.utils.deconstruct import deconstructible
16+
from django.utils.deprecation import RemovedInDjango50Warning
1517
from django.utils.functional import cached_property
1618
from django.utils.hashable import make_hashable
1719

@@ -1513,11 +1515,20 @@ class OrderBy(Expression):
15131515
template = "%(expression)s %(ordering)s"
15141516
conditional = False
15151517

1516-
def __init__(
1517-
self, expression, descending=False, nulls_first=False, nulls_last=False
1518-
):
1518+
def __init__(self, expression, descending=False, nulls_first=None, nulls_last=None):
15191519
if nulls_first and nulls_last:
15201520
raise ValueError("nulls_first and nulls_last are mutually exclusive")
1521+
if nulls_first is False or nulls_last is False:
1522+
# When the deprecation ends, replace with:
1523+
# raise ValueError(
1524+
# "nulls_first and nulls_last values must be True or None."
1525+
# )
1526+
warnings.warn(
1527+
"Passing nulls_first=False or nulls_last=False is deprecated, use None "
1528+
"instead.",
1529+
RemovedInDjango50Warning,
1530+
stacklevel=2,
1531+
)
15211532
self.nulls_first = nulls_first
15221533
self.nulls_last = nulls_last
15231534
self.descending = descending
@@ -1584,9 +1595,12 @@ def get_group_by_cols(self, alias=None):
15841595

15851596
def reverse_ordering(self):
15861597
self.descending = not self.descending
1587-
if self.nulls_first or self.nulls_last:
1588-
self.nulls_first = not self.nulls_first
1589-
self.nulls_last = not self.nulls_last
1598+
if self.nulls_first:
1599+
self.nulls_last = True
1600+
self.nulls_first = None
1601+
elif self.nulls_last:
1602+
self.nulls_first = True
1603+
self.nulls_last = None
15901604
return self
15911605

15921606
def asc(self):

docs/internals/deprecation.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ details on these changes.
105105

106106
* The ``django.contrib.auth.hashers.CryptPasswordHasher`` will be removed.
107107

108+
* The ability to pass ``nulls_first=False`` or ``nulls_last=False`` to
109+
``Expression.asc()`` and ``Expression.desc()`` methods, and the ``OrderBy``
110+
expression will be removed.
111+
108112
.. _deprecation-removed-in-4.1:
109113

110114
4.1

docs/ref/models/expressions.txt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,20 +1033,40 @@ calling the appropriate methods on the wrapped expression.
10331033
to a column. The ``alias`` parameter will be ``None`` unless the
10341034
expression has been annotated and is used for grouping.
10351035

1036-
.. method:: asc(nulls_first=False, nulls_last=False)
1036+
.. method:: asc(nulls_first=None, nulls_last=None)
10371037

10381038
Returns the expression ready to be sorted in ascending order.
10391039

10401040
``nulls_first`` and ``nulls_last`` define how null values are sorted.
10411041
See :ref:`using-f-to-sort-null-values` for example usage.
10421042

1043-
.. method:: desc(nulls_first=False, nulls_last=False)
1043+
.. versionchanged:: 4.1
1044+
1045+
In older versions, ``nulls_first`` and ``nulls_last`` defaulted to
1046+
``False``.
1047+
1048+
.. deprecated:: 4.1
1049+
1050+
Passing ``nulls_first=False`` or ``nulls_last=False`` to ``asc()``
1051+
is deprecated. Use ``None`` instead.
1052+
1053+
.. method:: desc(nulls_first=None, nulls_last=None)
10441054

10451055
Returns the expression ready to be sorted in descending order.
10461056

10471057
``nulls_first`` and ``nulls_last`` define how null values are sorted.
10481058
See :ref:`using-f-to-sort-null-values` for example usage.
10491059

1060+
.. versionchanged:: 4.1
1061+
1062+
In older versions, ``nulls_first`` and ``nulls_last`` defaulted to
1063+
``False``.
1064+
1065+
.. deprecated:: 4.1
1066+
1067+
Passing ``nulls_first=False`` or ``nulls_last=False`` to ``desc()``
1068+
is deprecated. Use ``None`` instead.
1069+
10501070
.. method:: reverse_ordering()
10511071

10521072
Returns ``self`` with any modifications required to reverse the sort

docs/releases/4.1.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,10 @@ Miscellaneous
685685

686686
* ``django.contrib.auth.hashers.CryptPasswordHasher`` is deprecated.
687687

688+
* The ability to pass ``nulls_first=False`` or ``nulls_last=False`` to
689+
``Expression.asc()`` and ``Expression.desc()`` methods, and the ``OrderBy``
690+
expression is deprecated. Use ``None`` instead.
691+
688692
Features removed in 4.1
689693
=======================
690694

tests/expressions/tests.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
isolate_apps,
7070
register_lookup,
7171
)
72+
from django.utils.deprecation import RemovedInDjango50Warning
7273
from django.utils.functional import SimpleLazyObject
7374

7475
from .models import (
@@ -2537,7 +2538,7 @@ def test_equal(self):
25372538
)
25382539
self.assertNotEqual(
25392540
OrderBy(F("field"), nulls_last=True),
2540-
OrderBy(F("field"), nulls_last=False),
2541+
OrderBy(F("field")),
25412542
)
25422543

25432544
def test_hash(self):
@@ -2547,5 +2548,22 @@ def test_hash(self):
25472548
)
25482549
self.assertNotEqual(
25492550
hash(OrderBy(F("field"), nulls_last=True)),
2550-
hash(OrderBy(F("field"), nulls_last=False)),
2551+
hash(OrderBy(F("field"))),
25512552
)
2553+
2554+
def test_nulls_false(self):
2555+
# These tests will catch ValueError in Django 5.0 when passing False to
2556+
# nulls_first and nulls_last becomes forbidden.
2557+
# msg = "nulls_first and nulls_last values must be True or None."
2558+
msg = (
2559+
"Passing nulls_first=False or nulls_last=False is deprecated, use None "
2560+
"instead."
2561+
)
2562+
with self.assertRaisesMessage(RemovedInDjango50Warning, msg):
2563+
OrderBy(F("field"), nulls_first=False)
2564+
with self.assertRaisesMessage(RemovedInDjango50Warning, msg):
2565+
OrderBy(F("field"), nulls_last=False)
2566+
with self.assertRaisesMessage(RemovedInDjango50Warning, msg):
2567+
F("field").asc(nulls_first=False)
2568+
with self.assertRaisesMessage(RemovedInDjango50Warning, msg):
2569+
F("field").desc(nulls_last=False)

0 commit comments

Comments
 (0)