1
0
mirror of https://github.com/django/django.git synced 2025-03-06 15:32:33 +00:00

[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 <charette.s@gmail.com>

Backport of 0bf412111be686b6b23e00863f5d449d63557dbf from main.
This commit is contained in:
Jacob Walls 2025-02-16 21:35:12 -05:00 committed by Sarah Boyce
parent bb4f65ec87
commit 9525135698
3 changed files with 41 additions and 8 deletions

View File

@ -99,18 +99,23 @@ class JSONField(CheckFieldDefaultMixin, Field):
def get_db_prep_value(self, value, connection, prepared=False): def get_db_prep_value(self, value, connection, prepared=False):
if not prepared: if not prepared:
value = self.get_prep_value(value) value = self.get_prep_value(value)
if isinstance(value, expressions.Value) and isinstance(
value.output_field, JSONField
):
value = value.value
elif hasattr(value, "as_sql"):
return value
return connection.ops.adapt_json_value(value, self.encoder) return connection.ops.adapt_json_value(value, self.encoder)
def get_db_prep_save(self, value, connection): def get_db_prep_save(self, value, connection):
# This slightly involved logic is to allow for `None` to be used to
# store SQL `NULL` while `Value(None, JSONField())` can be used to
# store JSON `null` while preventing compilable `as_sql` values from
# making their way to `get_db_prep_value`, which is what the `super()`
# implementation does.
if value is None: if value is None:
return value return value
return self.get_db_prep_value(value, connection) if (
isinstance(value, expressions.Value)
and value.value is None
and isinstance(value.output_field, JSONField)
):
value = None
return super().get_db_prep_save(value, connection)
def get_transform(self, name): def get_transform(self, name):
transform = super().get_transform(name) transform = super().get_transform(name)

View File

@ -430,6 +430,17 @@ class RelatedJSONModel(models.Model):
required_db_features = {"supports_json_field"} required_db_features = {"supports_json_field"}
class CustomSerializationJSONModel(models.Model):
class StringifiedJSONField(models.JSONField):
def get_prep_value(self, value):
return json.dumps(value, cls=self.encoder)
json_field = StringifiedJSONField()
class Meta:
required_db_features = {"supports_json_field"}
class AllFieldsModel(models.Model): class AllFieldsModel(models.Model):
big_integer = models.BigIntegerField() big_integer = models.BigIntegerField()
binary = models.BinaryField() binary = models.BinaryField()

View File

@ -40,7 +40,13 @@ from django.db.models.functions import Cast
from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
from django.test.utils import CaptureQueriesContext from django.test.utils import CaptureQueriesContext
from .models import CustomJSONDecoder, JSONModel, NullableJSONModel, RelatedJSONModel from .models import (
CustomJSONDecoder,
CustomSerializationJSONModel,
JSONModel,
NullableJSONModel,
RelatedJSONModel,
)
@skipUnlessDBFeature("supports_json_field") @skipUnlessDBFeature("supports_json_field")
@ -298,6 +304,17 @@ class TestSaveLoad(TestCase):
obj.refresh_from_db() obj.refresh_from_db()
self.assertEqual(obj.value, value) self.assertEqual(obj.value, value)
def test_bulk_update_custom_get_prep_value(self):
objs = CustomSerializationJSONModel.objects.bulk_create(
[CustomSerializationJSONModel(pk=1, json_field={"version": "1"})]
)
objs[0].json_field["version"] = "1-alpha"
CustomSerializationJSONModel.objects.bulk_update(objs, ["json_field"])
self.assertSequenceEqual(
CustomSerializationJSONModel.objects.values("json_field"),
[{"json_field": '{"version": "1-alpha"}'}],
)
@skipUnlessDBFeature("supports_json_field") @skipUnlessDBFeature("supports_json_field")
class TestQuerying(TestCase): class TestQuerying(TestCase):