mirror of
https://github.com/django/django.git
synced 2025-11-07 07:15:35 +00:00
Refs #35381 -- Deprecated using None in JSONExact rhs to mean JSON null.
Key and index lookups are exempt from the deprecation. Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
This commit is contained in:
committed by
Jacob Walls
parent
be7f68422d
commit
348ca84538
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core import checks, exceptions
|
from django.core import checks, exceptions
|
||||||
@@ -11,6 +12,7 @@ from django.db.models.lookups import (
|
|||||||
PostgresOperatorLookup,
|
PostgresOperatorLookup,
|
||||||
Transform,
|
Transform,
|
||||||
)
|
)
|
||||||
|
from django.utils.deprecation import RemovedInDjango70Warning, django_file_prefixes
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from . import Field
|
from . import Field
|
||||||
@@ -332,10 +334,24 @@ class CaseInsensitiveMixin:
|
|||||||
|
|
||||||
|
|
||||||
class JSONExact(lookups.Exact):
|
class JSONExact(lookups.Exact):
|
||||||
|
# RemovedInDjango70Warning: When the deprecation period is over, remove
|
||||||
|
# the following line.
|
||||||
can_use_none_as_rhs = True
|
can_use_none_as_rhs = True
|
||||||
|
|
||||||
def process_rhs(self, compiler, connection):
|
def process_rhs(self, compiler, connection):
|
||||||
|
if self.rhs is None and not isinstance(self.lhs, KeyTransform):
|
||||||
|
warnings.warn(
|
||||||
|
"Using None as the right-hand side of an exact lookup on JSONField to "
|
||||||
|
"mean JSON scalar 'null' is deprecated. Use JSONNull() instead (or use "
|
||||||
|
"the __isnull lookup if you meant SQL NULL).",
|
||||||
|
RemovedInDjango70Warning,
|
||||||
|
skip_file_prefixes=django_file_prefixes(),
|
||||||
|
)
|
||||||
|
|
||||||
rhs, rhs_params = super().process_rhs(compiler, connection)
|
rhs, rhs_params = super().process_rhs(compiler, connection)
|
||||||
|
|
||||||
|
# RemovedInDjango70Warning: When the deprecation period is over, remove
|
||||||
|
# The following if-block entirely.
|
||||||
# Treat None lookup values as null.
|
# Treat None lookup values as null.
|
||||||
if rhs == "%s" and (*rhs_params,) == (None,):
|
if rhs == "%s" and (*rhs_params,) == (None,):
|
||||||
rhs_params = ("null",)
|
rhs_params = ("null",)
|
||||||
@@ -547,6 +563,10 @@ class KeyTransformIn(lookups.In):
|
|||||||
|
|
||||||
|
|
||||||
class KeyTransformExact(JSONExact):
|
class KeyTransformExact(JSONExact):
|
||||||
|
# RemovedInDjango70Warning: When deprecation period ends, uncomment the
|
||||||
|
# flag below.
|
||||||
|
# can_use_none_as_rhs = True
|
||||||
|
|
||||||
def process_rhs(self, compiler, connection):
|
def process_rhs(self, compiler, connection):
|
||||||
if isinstance(self.rhs, KeyTransform):
|
if isinstance(self.rhs, KeyTransform):
|
||||||
return super(lookups.Exact, self).process_rhs(compiler, connection)
|
return super(lookups.Exact, self).process_rhs(compiler, connection)
|
||||||
|
|||||||
@@ -360,6 +360,13 @@ Miscellaneous
|
|||||||
is deprecated. Pass an explicit field name, like
|
is deprecated. Pass an explicit field name, like
|
||||||
``values_list("pk", flat=True)``.
|
``values_list("pk", flat=True)``.
|
||||||
|
|
||||||
|
* The use of ``None`` to represent a top-level JSON scalar ``null`` when
|
||||||
|
querying :class:`~django.db.models.JSONField` is now deprecated in favor of
|
||||||
|
the new :class:`~django.db.models.JSONNull` expression. At the end
|
||||||
|
of the deprecation period, ``None`` values compile to SQL ``IS NULL`` when
|
||||||
|
used as the top-level value. :lookup:`Key and index lookups <jsonfield.key>`
|
||||||
|
are unaffected by this deprecation.
|
||||||
|
|
||||||
Features removed in 6.1
|
Features removed in 6.1
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
|||||||
@@ -1069,6 +1069,11 @@ as JSON ``null``.
|
|||||||
When querying, :lookup:`isnull=True <isnull>` is used to match SQL ``NULL``,
|
When querying, :lookup:`isnull=True <isnull>` is used to match SQL ``NULL``,
|
||||||
while exact-matching ``JSONNull()`` is used to match JSON ``null``.
|
while exact-matching ``JSONNull()`` is used to match JSON ``null``.
|
||||||
|
|
||||||
|
.. deprecated:: 6.1
|
||||||
|
|
||||||
|
Exact-matching ``None`` in a query to mean JSON ``null`` is deprecated.
|
||||||
|
After the deprecation period, it will be interpreted as SQL ``NULL``.
|
||||||
|
|
||||||
.. versionchanged:: 6.1
|
.. versionchanged:: 6.1
|
||||||
|
|
||||||
``JSONNull()`` expression was added.
|
``JSONNull()`` expression was added.
|
||||||
@@ -1080,6 +1085,12 @@ while exact-matching ``JSONNull()`` is used to match JSON ``null``.
|
|||||||
<Dog: Max>
|
<Dog: Max>
|
||||||
>>> Dog.objects.create(name="Archie", data=JSONNull()) # JSON null.
|
>>> Dog.objects.create(name="Archie", data=JSONNull()) # JSON null.
|
||||||
<Dog: Archie>
|
<Dog: Archie>
|
||||||
|
>>> Dog.objects.filter(data=None)
|
||||||
|
...: RemovedInDjango70Warning: Using None as the right-hand side of an
|
||||||
|
exact lookup on JSONField to mean JSON scalar 'null' is deprecated. Use
|
||||||
|
JSONNull() instead (or use the __isnull lookup if you meant SQL NULL).
|
||||||
|
...
|
||||||
|
<QuerySet [<Dog: Archie>]>
|
||||||
>>> Dog.objects.filter(data=JSONNull())
|
>>> Dog.objects.filter(data=JSONNull())
|
||||||
<QuerySet [<Dog: Archie>]>
|
<QuerySet [<Dog: Archie>]>
|
||||||
>>> Dog.objects.filter(data__isnull=True)
|
>>> Dog.objects.filter(data__isnull=True)
|
||||||
@@ -1087,6 +1098,9 @@ while exact-matching ``JSONNull()`` is used to match JSON ``null``.
|
|||||||
>>> Dog.objects.filter(data__isnull=False)
|
>>> Dog.objects.filter(data__isnull=False)
|
||||||
<QuerySet [<Dog: Archie>]>
|
<QuerySet [<Dog: Archie>]>
|
||||||
|
|
||||||
|
.. RemovedInDjango70Warning: Alter the example with the deprecation warning to:
|
||||||
|
<QuerySet [<Dog: Max>]>.
|
||||||
|
|
||||||
Unless you are sure you wish to work with SQL ``NULL`` values, consider setting
|
Unless you are sure you wish to work with SQL ``NULL`` values, consider setting
|
||||||
``null=False`` and providing a suitable default for empty values, such as
|
``null=False`` and providing a suitable default for empty values, such as
|
||||||
``default=dict``.
|
``default=dict``.
|
||||||
|
|||||||
@@ -41,8 +41,15 @@ from django.db.models.fields.json import (
|
|||||||
KeyTransformTextLookupMixin,
|
KeyTransformTextLookupMixin,
|
||||||
)
|
)
|
||||||
from django.db.models.functions import Cast
|
from django.db.models.functions import Cast
|
||||||
from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
|
from django.test import (
|
||||||
|
SimpleTestCase,
|
||||||
|
TestCase,
|
||||||
|
ignore_warnings,
|
||||||
|
skipIfDBFeature,
|
||||||
|
skipUnlessDBFeature,
|
||||||
|
)
|
||||||
from django.test.utils import CaptureQueriesContext
|
from django.test.utils import CaptureQueriesContext
|
||||||
|
from django.utils.deprecation import RemovedInDjango70Warning
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
CustomJSONDecoder,
|
CustomJSONDecoder,
|
||||||
@@ -229,6 +236,8 @@ class TestSaveLoad(TestCase):
|
|||||||
self.assertIsNone(obj.value)
|
self.assertIsNone(obj.value)
|
||||||
|
|
||||||
@skipUnlessDBFeature("supports_primitives_in_json_field")
|
@skipUnlessDBFeature("supports_primitives_in_json_field")
|
||||||
|
# RemovedInDjango70Warning.
|
||||||
|
@ignore_warnings(category=RemovedInDjango70Warning)
|
||||||
def test_json_null_different_from_sql_null(self):
|
def test_json_null_different_from_sql_null(self):
|
||||||
json_null = NullableJSONModel.objects.create(value=Value(None, JSONField()))
|
json_null = NullableJSONModel.objects.create(value=Value(None, JSONField()))
|
||||||
NullableJSONModel.objects.update(value=Value(None, JSONField()))
|
NullableJSONModel.objects.update(value=Value(None, JSONField()))
|
||||||
@@ -242,6 +251,9 @@ class TestSaveLoad(TestCase):
|
|||||||
)
|
)
|
||||||
self.assertSequenceEqual(
|
self.assertSequenceEqual(
|
||||||
NullableJSONModel.objects.filter(value=None),
|
NullableJSONModel.objects.filter(value=None),
|
||||||
|
# RemovedInDjango70Warning: When the deprecation ends, replace
|
||||||
|
# with:
|
||||||
|
# [sql_null],
|
||||||
[json_null],
|
[json_null],
|
||||||
)
|
)
|
||||||
self.assertSequenceEqual(
|
self.assertSequenceEqual(
|
||||||
@@ -1365,3 +1377,36 @@ class JSONNullTests(TestCase):
|
|||||||
obj.refresh_from_db()
|
obj.refresh_from_db()
|
||||||
self.assertIsNone(obj.value["name"])
|
self.assertIsNone(obj.value["name"])
|
||||||
self.assertEqual(obj.value["array"], [1, None])
|
self.assertEqual(obj.value["array"], [1, None])
|
||||||
|
|
||||||
|
|
||||||
|
# RemovedInDjango70Warning.
|
||||||
|
@skipUnlessDBFeature("supports_primitives_in_json_field")
|
||||||
|
class JSONExactNoneDeprecationTests(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.msg = (
|
||||||
|
"Using None as the right-hand side of an exact lookup on JSONField to mean "
|
||||||
|
"JSON scalar 'null' is deprecated. Use JSONNull() instead (or use the "
|
||||||
|
"__isnull lookup if you meant SQL NULL)."
|
||||||
|
)
|
||||||
|
cls.obj = NullableJSONModel.objects.create(value=JSONNull())
|
||||||
|
|
||||||
|
def test_filter(self):
|
||||||
|
with self.assertWarnsMessage(RemovedInDjango70Warning, self.msg):
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
NullableJSONModel.objects.filter(value=None), [self.obj]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_annotation_q_filter(self):
|
||||||
|
qs = NullableJSONModel.objects.annotate(
|
||||||
|
has_empty_data=Q(value__isnull=True) | Q(value=None)
|
||||||
|
).filter(has_empty_data=True)
|
||||||
|
with self.assertWarnsMessage(RemovedInDjango70Warning, self.msg):
|
||||||
|
self.assertSequenceEqual(qs, [self.obj])
|
||||||
|
|
||||||
|
def test_case_when(self):
|
||||||
|
qs = NullableJSONModel.objects.annotate(
|
||||||
|
has_json_null=Case(When(value=None, then=Value(True)), default=Value(False))
|
||||||
|
).filter(has_json_null=True)
|
||||||
|
with self.assertWarnsMessage(RemovedInDjango70Warning, self.msg):
|
||||||
|
self.assertSequenceEqual(qs, [self.obj])
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from django.db.models.functions import Cast, JSONObject, Upper
|
|||||||
from django.test import TransactionTestCase, override_settings, skipUnlessDBFeature
|
from django.test import TransactionTestCase, override_settings, skipUnlessDBFeature
|
||||||
from django.test.utils import isolate_apps
|
from django.test.utils import isolate_apps
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.deprecation import RemovedInDjango70Warning
|
||||||
|
|
||||||
from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase, PostgreSQLWidgetTestCase
|
from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase, PostgreSQLWidgetTestCase
|
||||||
from .models import (
|
from .models import (
|
||||||
@@ -1586,6 +1587,17 @@ class TestJSONFieldQuerying(PostgreSQLTestCase):
|
|||||||
self.assertSequenceEqual(
|
self.assertSequenceEqual(
|
||||||
OtherTypesArrayModel.objects.filter(json__1__isnull=True), [obj]
|
OtherTypesArrayModel.objects.filter(json__1__isnull=True), [obj]
|
||||||
)
|
)
|
||||||
|
# RemovedInDjango70Warning.
|
||||||
|
msg = (
|
||||||
|
"Using None as the right-hand side of an exact lookup on JSONField to mean "
|
||||||
|
"JSON scalar 'null' is deprecated. Use JSONNull() instead (or use the "
|
||||||
|
"__isnull lookup if you meant SQL NULL)."
|
||||||
|
)
|
||||||
|
with self.assertWarnsMessage(RemovedInDjango70Warning, msg):
|
||||||
|
# RemovedInDjango70Warning: deindent, and replace [] with [obj].
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
OtherTypesArrayModel.objects.filter(json__1=None), []
|
||||||
|
)
|
||||||
|
|
||||||
def test_saving_and_querying_for_json_null(self):
|
def test_saving_and_querying_for_json_null(self):
|
||||||
obj = OtherTypesArrayModel.objects.create(json=[JSONNull(), JSONNull()])
|
obj = OtherTypesArrayModel.objects.create(json=[JSONNull(), JSONNull()])
|
||||||
|
|||||||
Reference in New Issue
Block a user