Fixed #30841 -- Deprecated using non-boolean values for isnull lookup.
This commit is contained in:
parent
2f72480fbd
commit
31174031f1
|
@ -1,5 +1,6 @@
|
||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
|
import warnings
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
from django.core.exceptions import EmptyResultSet
|
from django.core.exceptions import EmptyResultSet
|
||||||
|
@ -9,6 +10,7 @@ from django.db.models.fields import (
|
||||||
)
|
)
|
||||||
from django.db.models.query_utils import RegisterLookupMixin
|
from django.db.models.query_utils import RegisterLookupMixin
|
||||||
from django.utils.datastructures import OrderedSet
|
from django.utils.datastructures import OrderedSet
|
||||||
|
from django.utils.deprecation import RemovedInDjango40Warning
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
|
|
||||||
|
@ -463,6 +465,17 @@ class IsNull(BuiltinLookup):
|
||||||
prepare_rhs = False
|
prepare_rhs = False
|
||||||
|
|
||||||
def as_sql(self, compiler, connection):
|
def as_sql(self, compiler, connection):
|
||||||
|
if not isinstance(self.rhs, bool):
|
||||||
|
# When the deprecation ends, replace with:
|
||||||
|
# raise ValueError(
|
||||||
|
# 'The QuerySet value for an isnull lookup must be True or '
|
||||||
|
# 'False.'
|
||||||
|
# )
|
||||||
|
warnings.warn(
|
||||||
|
'Using a non-boolean value for an isnull lookup is '
|
||||||
|
'deprecated, use True or False instead.',
|
||||||
|
RemovedInDjango40Warning,
|
||||||
|
)
|
||||||
sql, params = compiler.compile(self.lhs)
|
sql, params = compiler.compile(self.lhs)
|
||||||
if self.rhs:
|
if self.rhs:
|
||||||
return "%s IS NULL" % sql, params
|
return "%s IS NULL" % sql, params
|
||||||
|
|
|
@ -36,6 +36,9 @@ details on these changes.
|
||||||
|
|
||||||
* The ``PASSWORD_RESET_TIMEOUT_DAYS`` setting will be removed.
|
* The ``PASSWORD_RESET_TIMEOUT_DAYS`` setting will be removed.
|
||||||
|
|
||||||
|
* The undocumented usage of the :lookup:`isnull` lookup with non-boolean values
|
||||||
|
as the right-hand side will no longer be allowed.
|
||||||
|
|
||||||
See the :ref:`Django 3.1 release notes <deprecated-features-3.1>` for more
|
See the :ref:`Django 3.1 release notes <deprecated-features-3.1>` for more
|
||||||
details on these changes.
|
details on these changes.
|
||||||
|
|
||||||
|
|
|
@ -3281,6 +3281,11 @@ SQL equivalent:
|
||||||
|
|
||||||
SELECT ... WHERE pub_date IS NULL;
|
SELECT ... WHERE pub_date IS NULL;
|
||||||
|
|
||||||
|
.. deprecated:: 3.1
|
||||||
|
|
||||||
|
Using non-boolean values as the right-hand side is deprecated, use ``True``
|
||||||
|
or ``False`` instead. In Django 4.0, the exception will be raised.
|
||||||
|
|
||||||
.. fieldlookup:: regex
|
.. fieldlookup:: regex
|
||||||
|
|
||||||
``regex``
|
``regex``
|
||||||
|
|
|
@ -274,6 +274,9 @@ Miscellaneous
|
||||||
* ``PASSWORD_RESET_TIMEOUT_DAYS`` setting is deprecated in favor of
|
* ``PASSWORD_RESET_TIMEOUT_DAYS`` setting is deprecated in favor of
|
||||||
:setting:`PASSWORD_RESET_TIMEOUT`.
|
:setting:`PASSWORD_RESET_TIMEOUT`.
|
||||||
|
|
||||||
|
* The undocumented usage of the :lookup:`isnull` lookup with non-boolean values
|
||||||
|
as the right-hand side is deprecated, use ``True`` or ``False`` instead.
|
||||||
|
|
||||||
.. _removed-features-3.1:
|
.. _removed-features-3.1:
|
||||||
|
|
||||||
Features removed in 3.1
|
Features removed in 3.1
|
||||||
|
|
|
@ -96,3 +96,15 @@ class Product(models.Model):
|
||||||
class Stock(models.Model):
|
class Stock(models.Model):
|
||||||
product = models.ForeignKey(Product, models.CASCADE)
|
product = models.ForeignKey(Product, models.CASCADE)
|
||||||
qty_available = models.DecimalField(max_digits=6, decimal_places=2)
|
qty_available = models.DecimalField(max_digits=6, decimal_places=2)
|
||||||
|
|
||||||
|
|
||||||
|
class Freebie(models.Model):
|
||||||
|
gift_product = models.ForeignKey(Product, models.CASCADE)
|
||||||
|
stock_id = models.IntegerField(blank=True, null=True)
|
||||||
|
|
||||||
|
stock = models.ForeignObject(
|
||||||
|
Stock,
|
||||||
|
from_fields=['stock_id', 'gift_product'],
|
||||||
|
to_fields=['id', 'product'],
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
|
|
|
@ -9,9 +9,10 @@ from django.db.models import Max
|
||||||
from django.db.models.expressions import Exists, OuterRef
|
from django.db.models.expressions import Exists, OuterRef
|
||||||
from django.db.models.functions import Substr
|
from django.db.models.functions import Substr
|
||||||
from django.test import TestCase, skipUnlessDBFeature
|
from django.test import TestCase, skipUnlessDBFeature
|
||||||
|
from django.utils.deprecation import RemovedInDjango40Warning
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Article, Author, Game, IsNullWithNoneAsRHS, Player, Season, Tag,
|
Article, Author, Freebie, Game, IsNullWithNoneAsRHS, Player, Season, Tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -969,3 +970,24 @@ class LookupTests(TestCase):
|
||||||
).values('max_id')
|
).values('max_id')
|
||||||
authors = Author.objects.filter(id=authors_max_ids[:1])
|
authors = Author.objects.filter(id=authors_max_ids[:1])
|
||||||
self.assertEqual(authors.get(), newest_author)
|
self.assertEqual(authors.get(), newest_author)
|
||||||
|
|
||||||
|
def test_isnull_non_boolean_value(self):
|
||||||
|
# These tests will catch ValueError in Django 4.0 when using
|
||||||
|
# non-boolean values for an isnull lookup becomes forbidden.
|
||||||
|
# msg = (
|
||||||
|
# 'The QuerySet value for an isnull lookup must be True or False.'
|
||||||
|
# )
|
||||||
|
msg = (
|
||||||
|
'Using a non-boolean value for an isnull lookup is deprecated, '
|
||||||
|
'use True or False instead.'
|
||||||
|
)
|
||||||
|
tests = [
|
||||||
|
Author.objects.filter(alias__isnull=1),
|
||||||
|
Article.objects.filter(author__isnull=1),
|
||||||
|
Season.objects.filter(games__isnull=1),
|
||||||
|
Freebie.objects.filter(stock__isnull=1),
|
||||||
|
]
|
||||||
|
for qs in tests:
|
||||||
|
with self.subTest(qs=qs):
|
||||||
|
with self.assertWarnsMessage(RemovedInDjango40Warning, msg):
|
||||||
|
qs.exists()
|
||||||
|
|
Loading…
Reference in New Issue