Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions django/db/models/sql/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
Value,
)
from django.db.models.fields import Field
from django.db.models.fields.json import KeyTransform
from django.db.models.lookups import Lookup
from django.db.models.query_utils import (
Q,
Expand Down Expand Up @@ -1437,11 +1438,15 @@ def build_lookup(self, lookups, lhs, rhs):
return

lookup = lookup_class(lhs, rhs)
# Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
# uses of None as a query value unless the lookup supports it.
# Interpret '__exact=None' as the SQL 'IS NULL'. For '__iexact=None' on
# KeyTransform, interpret it as '__exact=None' instead of 'IS NULL'
# (#36508). For all other cases, reject the use of None as a query
# value unless the lookup explicitly supports it.
if lookup.rhs is None and not lookup.can_use_none_as_rhs:
if lookup_name not in ("exact", "iexact"):
raise ValueError("Cannot use None as a query value")
if lookup_name == "iexact" and isinstance(lhs, KeyTransform):
return lhs.get_lookup("exact")(lhs, None)
return lhs.get_lookup("isnull")(lhs, True)

# For Oracle '' is equivalent to null. The check must be done at this
Expand Down
5 changes: 5 additions & 0 deletions docs/releases/6.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,11 @@ Miscellaneous
deleted files when ``--verbosity`` is 1. To see the details for each file
deleted, set the ``--verbosity`` flag to 2 or higher.

* The :lookup:`iexact=None <iexact>` lookup on
:class:`~django.db.models.JSONField` key transforms now match JSON ``null``,
to match the behavior of :lookup:`exact=None <exact>` on key transforms.
Previously, it was interpreted as an :lookup:`isnull` lookup.

.. _deprecated-features-6.0:

Features deprecated in 6.0
Expand Down
20 changes: 13 additions & 7 deletions tests/model_fields/test_jsonfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,14 +347,14 @@ def setUpTestData(cls):
"bax": {"foo": "bar"},
},
]
cls.objs = [NullableJSONModel.objects.create(value=value) for value in values]
objs = [NullableJSONModel(value=value) for value in values]
if connection.features.supports_primitives_in_json_field:
cls.objs.extend(
[
NullableJSONModel.objects.create(value=value)
for value in cls.primitives
]
)
objs.extend([NullableJSONModel(value=value) for value in cls.primitives])
objs = NullableJSONModel.objects.bulk_create(objs)
# Some backends don't return primary keys after bulk_create.
if any(obj.pk is None for obj in objs):
objs = list(NullableJSONModel.objects.all())
cls.objs = objs
cls.raw_sql = "%s::jsonb" if connection.vendor == "postgresql" else "%s"

def test_exact(self):
Expand Down Expand Up @@ -765,6 +765,12 @@ def test_none_key(self):
[self.objs[4]],
)

def test_key_iexact_none(self):
self.assertSequenceEqual(
NullableJSONModel.objects.filter(value__j__iexact=None),
[self.objs[4]],
)

def test_none_key_exclude(self):
obj = NullableJSONModel.objects.create(value={"j": 1})
if connection.vendor == "oracle":
Expand Down
Loading