Fixed #33543 -- Deprecated passing nulls_first/nulls_last=False to OrderBy and Expression.asc()/desc().

Thanks Allen Jonathan David for the initial patch.
This commit is contained in:
Mariusz Felisiak 2022-05-12 11:30:03 +02:00 committed by GitHub
parent 2798c937de
commit 68da6b389c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 10 deletions

View File

@ -2,6 +2,7 @@ import copy
import datetime import datetime
import functools import functools
import inspect import inspect
import warnings
from collections import defaultdict from collections import defaultdict
from decimal import Decimal from decimal import Decimal
from uuid import UUID from uuid import UUID
@ -12,6 +13,7 @@ from django.db.models import fields
from django.db.models.constants import LOOKUP_SEP from django.db.models.constants import LOOKUP_SEP
from django.db.models.query_utils import Q from django.db.models.query_utils import Q
from django.utils.deconstruct import deconstructible from django.utils.deconstruct import deconstructible
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.hashable import make_hashable from django.utils.hashable import make_hashable
@ -1513,11 +1515,20 @@ class OrderBy(Expression):
template = "%(expression)s %(ordering)s" template = "%(expression)s %(ordering)s"
conditional = False conditional = False
def __init__( def __init__(self, expression, descending=False, nulls_first=None, nulls_last=None):
self, expression, descending=False, nulls_first=False, nulls_last=False
):
if nulls_first and nulls_last: if nulls_first and nulls_last:
raise ValueError("nulls_first and nulls_last are mutually exclusive") raise ValueError("nulls_first and nulls_last are mutually exclusive")
if nulls_first is False or nulls_last is False:
# When the deprecation ends, replace with:
# raise ValueError(
# "nulls_first and nulls_last values must be True or None."
# )
warnings.warn(
"Passing nulls_first=False or nulls_last=False is deprecated, use None "
"instead.",
RemovedInDjango50Warning,
stacklevel=2,
)
self.nulls_first = nulls_first self.nulls_first = nulls_first
self.nulls_last = nulls_last self.nulls_last = nulls_last
self.descending = descending self.descending = descending
@ -1584,9 +1595,12 @@ class OrderBy(Expression):
def reverse_ordering(self): def reverse_ordering(self):
self.descending = not self.descending self.descending = not self.descending
if self.nulls_first or self.nulls_last: if self.nulls_first:
self.nulls_first = not self.nulls_first self.nulls_last = True
self.nulls_last = not self.nulls_last self.nulls_first = None
elif self.nulls_last:
self.nulls_first = True
self.nulls_last = None
return self return self
def asc(self): def asc(self):

View File

@ -105,6 +105,10 @@ details on these changes.
* The ``django.contrib.auth.hashers.CryptPasswordHasher`` will be removed. * The ``django.contrib.auth.hashers.CryptPasswordHasher`` will be removed.
* The ability to pass ``nulls_first=False`` or ``nulls_last=False`` to
``Expression.asc()`` and ``Expression.desc()`` methods, and the ``OrderBy``
expression will be removed.
.. _deprecation-removed-in-4.1: .. _deprecation-removed-in-4.1:
4.1 4.1

View File

@ -1033,20 +1033,40 @@ calling the appropriate methods on the wrapped expression.
to a column. The ``alias`` parameter will be ``None`` unless the to a column. The ``alias`` parameter will be ``None`` unless the
expression has been annotated and is used for grouping. expression has been annotated and is used for grouping.
.. method:: asc(nulls_first=False, nulls_last=False) .. method:: asc(nulls_first=None, nulls_last=None)
Returns the expression ready to be sorted in ascending order. Returns the expression ready to be sorted in ascending order.
``nulls_first`` and ``nulls_last`` define how null values are sorted. ``nulls_first`` and ``nulls_last`` define how null values are sorted.
See :ref:`using-f-to-sort-null-values` for example usage. See :ref:`using-f-to-sort-null-values` for example usage.
.. method:: desc(nulls_first=False, nulls_last=False) .. versionchanged:: 4.1
In older versions, ``nulls_first`` and ``nulls_last`` defaulted to
``False``.
.. deprecated:: 4.1
Passing ``nulls_first=False`` or ``nulls_last=False`` to ``asc()``
is deprecated. Use ``None`` instead.
.. method:: desc(nulls_first=None, nulls_last=None)
Returns the expression ready to be sorted in descending order. Returns the expression ready to be sorted in descending order.
``nulls_first`` and ``nulls_last`` define how null values are sorted. ``nulls_first`` and ``nulls_last`` define how null values are sorted.
See :ref:`using-f-to-sort-null-values` for example usage. See :ref:`using-f-to-sort-null-values` for example usage.
.. versionchanged:: 4.1
In older versions, ``nulls_first`` and ``nulls_last`` defaulted to
``False``.
.. deprecated:: 4.1
Passing ``nulls_first=False`` or ``nulls_last=False`` to ``desc()``
is deprecated. Use ``None`` instead.
.. method:: reverse_ordering() .. method:: reverse_ordering()
Returns ``self`` with any modifications required to reverse the sort Returns ``self`` with any modifications required to reverse the sort

View File

@ -685,6 +685,10 @@ Miscellaneous
* ``django.contrib.auth.hashers.CryptPasswordHasher`` is deprecated. * ``django.contrib.auth.hashers.CryptPasswordHasher`` is deprecated.
* The ability to pass ``nulls_first=False`` or ``nulls_last=False`` to
``Expression.asc()`` and ``Expression.desc()`` methods, and the ``OrderBy``
expression is deprecated. Use ``None`` instead.
Features removed in 4.1 Features removed in 4.1
======================= =======================

View File

@ -69,6 +69,7 @@ from django.test.utils import (
isolate_apps, isolate_apps,
register_lookup, register_lookup,
) )
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from .models import ( from .models import (
@ -2537,7 +2538,7 @@ class OrderByTests(SimpleTestCase):
) )
self.assertNotEqual( self.assertNotEqual(
OrderBy(F("field"), nulls_last=True), OrderBy(F("field"), nulls_last=True),
OrderBy(F("field"), nulls_last=False), OrderBy(F("field")),
) )
def test_hash(self): def test_hash(self):
@ -2547,5 +2548,22 @@ class OrderByTests(SimpleTestCase):
) )
self.assertNotEqual( self.assertNotEqual(
hash(OrderBy(F("field"), nulls_last=True)), hash(OrderBy(F("field"), nulls_last=True)),
hash(OrderBy(F("field"), nulls_last=False)), hash(OrderBy(F("field"))),
) )
def test_nulls_false(self):
# These tests will catch ValueError in Django 5.0 when passing False to
# nulls_first and nulls_last becomes forbidden.
# msg = "nulls_first and nulls_last values must be True or None."
msg = (
"Passing nulls_first=False or nulls_last=False is deprecated, use None "
"instead."
)
with self.assertRaisesMessage(RemovedInDjango50Warning, msg):
OrderBy(F("field"), nulls_first=False)
with self.assertRaisesMessage(RemovedInDjango50Warning, msg):
OrderBy(F("field"), nulls_last=False)
with self.assertRaisesMessage(RemovedInDjango50Warning, msg):
F("field").asc(nulls_first=False)
with self.assertRaisesMessage(RemovedInDjango50Warning, msg):
F("field").desc(nulls_last=False)