1
0
mirror of https://github.com/django/django.git synced 2025-11-07 07:15:35 +00:00

Refs #35381 -- Delegated ArrayField element prepping to base_field.get_db_prep_save.

Previously, ArrayField always used base_field.get_db_prep_value when saving,
which could differ from how base_field prepares data for save. This change
overrides ArrayField.get_db_prep_save to delegate to the base_field's
get_db_prep_save, ensuring elements like None in JSONField arrays are saved
correctly as SQL NULL instead of JSON null.
This commit is contained in:
Clifford Gama
2025-10-21 11:34:58 +02:00
committed by Jacob Walls
parent adc25a9a66
commit be7f68422d
4 changed files with 42 additions and 1 deletions

View File

@@ -135,6 +135,11 @@ class ArrayField(CheckPostgresInstalledMixin, CheckFieldDefaultMixin, Field):
] ]
return value return value
def get_db_prep_save(self, value, connection):
if isinstance(value, (list, tuple)):
return [self.base_field.get_db_prep_save(i, connection) for i in value]
return value
def deconstruct(self): def deconstruct(self):
name, path, args, kwargs = super().deconstruct() name, path, args, kwargs = super().deconstruct()
if path == "django.contrib.postgres.fields.array.ArrayField": if path == "django.contrib.postgres.fields.array.ArrayField":

View File

@@ -319,6 +319,15 @@ backends.
* Support for PostGIS 3.1 is removed. * Support for PostGIS 3.1 is removed.
:mod:`django.contrib.postgres`
------------------------------
* Top-level elements set to ``None`` in an
:class:`~django.contrib.postgres.fields.ArrayField` with a
:class:`~django.db.models.JSONField` base field are now saved as SQL ``NULL``
instead of the JSON ``null`` primitive. This matches the behavior of a
standalone :class:`~django.db.models.JSONField` when storing ``None`` values.
Dropped support for PostgreSQL 14 Dropped support for PostgreSQL 14
--------------------------------- ---------------------------------

View File

@@ -79,7 +79,7 @@ class OtherTypesArrayModel(PostgreSQLModel):
models.DecimalField(max_digits=5, decimal_places=2), default=list models.DecimalField(max_digits=5, decimal_places=2), default=list
) )
tags = ArrayField(TagField(), blank=True, null=True) tags = ArrayField(TagField(), blank=True, null=True)
json = ArrayField(models.JSONField(default=dict), default=list) json = ArrayField(models.JSONField(default=dict), default=list, null=True)
int_ranges = ArrayField(IntegerRangeField(), blank=True, null=True) int_ranges = ArrayField(IntegerRangeField(), blank=True, null=True)
bigint_ranges = ArrayField(BigIntegerRangeField(), blank=True, null=True) bigint_ranges = ArrayField(BigIntegerRangeField(), blank=True, null=True)

View File

@@ -10,6 +10,7 @@ from django.core import checks, exceptions, serializers, validators
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.core.management import call_command from django.core.management import call_command
from django.db import IntegrityError, connection, models from django.db import IntegrityError, connection, models
from django.db.models import JSONNull
from django.db.models.expressions import Exists, F, OuterRef, RawSQL, Value from django.db.models.expressions import Exists, F, OuterRef, RawSQL, Value
from django.db.models.functions import Cast, JSONObject, Upper 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
@@ -1577,3 +1578,29 @@ class TestAdminUtils(PostgreSQLTestCase):
self.empty_value, self.empty_value,
) )
self.assertEqual(display_value, self.empty_value) self.assertEqual(display_value, self.empty_value)
class TestJSONFieldQuerying(PostgreSQLTestCase):
def test_saving_and_querying_for_sql_null(self):
obj = OtherTypesArrayModel.objects.create(json=[None, None])
self.assertSequenceEqual(
OtherTypesArrayModel.objects.filter(json__1__isnull=True), [obj]
)
def test_saving_and_querying_for_json_null(self):
obj = OtherTypesArrayModel.objects.create(json=[JSONNull(), JSONNull()])
self.assertSequenceEqual(
OtherTypesArrayModel.objects.filter(json__1=JSONNull()), [obj]
)
self.assertSequenceEqual(
OtherTypesArrayModel.objects.filter(json__1__isnull=True), []
)
def test_saving_and_querying_for_nested_json_nulls(self):
obj = OtherTypesArrayModel.objects.create(json=[[None, 1], [None, 2]])
self.assertSequenceEqual(
OtherTypesArrayModel.objects.filter(json__1__0=None), [obj]
)
self.assertSequenceEqual(
OtherTypesArrayModel.objects.filter(json__1__0__isnull=True), []
)