Fixed #30841 -- Deprecated using non-boolean values for isnull lookup.

This commit is contained in:
André Ericson 2019-10-11 20:10:31 +02:00 committed by Mariusz Felisiak
parent 2f72480fbd
commit 31174031f1
6 changed files with 59 additions and 1 deletions

View File

@ -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

View File

@ -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.

View File

@ -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``

View File

@ -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

View File

@ -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,
)

View File

@ -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()