Skip to content

Commit 9525135

Browse files
jacobtylerwallssarahboyce
authored andcommitted
[5.2.x] Fixed #35167 -- Delegated to super() in JSONField.get_db_prep_save().
Avoids reports of bulk_update() sending Cast expressions to JSONField.get_prep_value(). Co-authored-by: Simon Charette <[email protected]> Backport of 0bf4121 from main.
1 parent bb4f65e commit 9525135

File tree

3 files changed

+41
-8
lines changed

3 files changed

+41
-8
lines changed

django/db/models/fields/json.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,23 @@ def get_internal_type(self):
9999
def get_db_prep_value(self, value, connection, prepared=False):
100100
if not prepared:
101101
value = self.get_prep_value(value)
102-
if isinstance(value, expressions.Value) and isinstance(
103-
value.output_field, JSONField
104-
):
105-
value = value.value
106-
elif hasattr(value, "as_sql"):
107-
return value
108102
return connection.ops.adapt_json_value(value, self.encoder)
109103

110104
def get_db_prep_save(self, value, connection):
105+
# This slightly involved logic is to allow for `None` to be used to
106+
# store SQL `NULL` while `Value(None, JSONField())` can be used to
107+
# store JSON `null` while preventing compilable `as_sql` values from
108+
# making their way to `get_db_prep_value`, which is what the `super()`
109+
# implementation does.
111110
if value is None:
112111
return value
113-
return self.get_db_prep_value(value, connection)
112+
if (
113+
isinstance(value, expressions.Value)
114+
and value.value is None
115+
and isinstance(value.output_field, JSONField)
116+
):
117+
value = None
118+
return super().get_db_prep_save(value, connection)
114119

115120
def get_transform(self, name):
116121
transform = super().get_transform(name)

tests/model_fields/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,17 @@ class Meta:
430430
required_db_features = {"supports_json_field"}
431431

432432

433+
class CustomSerializationJSONModel(models.Model):
434+
class StringifiedJSONField(models.JSONField):
435+
def get_prep_value(self, value):
436+
return json.dumps(value, cls=self.encoder)
437+
438+
json_field = StringifiedJSONField()
439+
440+
class Meta:
441+
required_db_features = {"supports_json_field"}
442+
443+
433444
class AllFieldsModel(models.Model):
434445
big_integer = models.BigIntegerField()
435446
binary = models.BinaryField()

tests/model_fields/test_jsonfield.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@
4040
from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
4141
from django.test.utils import CaptureQueriesContext
4242

43-
from .models import CustomJSONDecoder, JSONModel, NullableJSONModel, RelatedJSONModel
43+
from .models import (
44+
CustomJSONDecoder,
45+
CustomSerializationJSONModel,
46+
JSONModel,
47+
NullableJSONModel,
48+
RelatedJSONModel,
49+
)
4450

4551

4652
@skipUnlessDBFeature("supports_json_field")
@@ -298,6 +304,17 @@ def test_realistic_object(self):
298304
obj.refresh_from_db()
299305
self.assertEqual(obj.value, value)
300306

307+
def test_bulk_update_custom_get_prep_value(self):
308+
objs = CustomSerializationJSONModel.objects.bulk_create(
309+
[CustomSerializationJSONModel(pk=1, json_field={"version": "1"})]
310+
)
311+
objs[0].json_field["version"] = "1-alpha"
312+
CustomSerializationJSONModel.objects.bulk_update(objs, ["json_field"])
313+
self.assertSequenceEqual(
314+
CustomSerializationJSONModel.objects.values("json_field"),
315+
[{"json_field": '{"version": "1-alpha"}'}],
316+
)
317+
301318

302319
@skipUnlessDBFeature("supports_json_field")
303320
class TestQuerying(TestCase):

0 commit comments

Comments
 (0)