1
0
mirror of https://github.com/django/django.git synced 2025-07-04 01:39:20 +00:00

[5.2.x] Fixed #36419 -- Ensured for_save was propagated when resolving expressions.

The for_save flag wasn't properly propagated when resolving expressions, which
prevented get_db_prep_save() from being called in some cases. This affected
fields like JSONField where None would be saved as JSON null instead of SQL NULL.

Regression in 00c690efbc0b10f67924687f24a7b30397bf47d9.

Thanks to David Sanders and Simon Charette for reviews.

Co-authored-by: Adam Johnson <me@adamj.eu>

Backport of c1fa3fdd040718356e5a3b9a0fe699d73f47a940 from main.
This commit is contained in:
Clifford Gama 2025-05-26 16:44:08 +02:00 committed by Sarah Boyce
parent 8fcc83953c
commit 6fc620b4a8
3 changed files with 21 additions and 2 deletions

View File

@ -297,7 +297,7 @@ class BaseExpression:
c.is_summary = summarize c.is_summary = summarize
source_expressions = [ source_expressions = [
( (
expr.resolve_expression(query, allow_joins, reuse, summarize) expr.resolve_expression(query, allow_joins, reuse, summarize, for_save)
if expr is not None if expr is not None
else None else None
) )

View File

@ -13,3 +13,7 @@ Bugfixes
* Fixed a log injection possibility by migrating remaining response logging * Fixed a log injection possibility by migrating remaining response logging
to ``django.utils.log.log_response()``, which safely escapes arguments such to ``django.utils.log.log_response()``, which safely escapes arguments such
as the request path to prevent unsafe log output (:cve:`2025-48432`). as the request path to prevent unsafe log output (:cve:`2025-48432`).
* Fixed a regression in Django 5.2 that caused :meth:`.QuerySet.bulk_update` to
incorrectly convert ``None`` to JSON ``null`` instead of SQL ``NULL`` for
``JSONField`` (:ticket:`36419`).

View File

@ -2,7 +2,7 @@ import datetime
from django.core.exceptions import FieldDoesNotExist from django.core.exceptions import FieldDoesNotExist
from django.db.models import F from django.db.models import F
from django.db.models.functions import Lower from django.db.models.functions import Coalesce, Lower
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.test import TestCase, override_settings, skipUnlessDBFeature from django.test import TestCase, override_settings, skipUnlessDBFeature
@ -289,6 +289,21 @@ class BulkUpdateTests(TestCase):
JSONFieldNullable.objects.filter(json_field__has_key="c"), objs JSONFieldNullable.objects.filter(json_field__has_key="c"), objs
) )
@skipUnlessDBFeature("supports_json_field")
def test_json_field_sql_null(self):
obj = JSONFieldNullable.objects.create(json_field={})
test_cases = [
("direct_none_assignment", None),
("expression_none_assignment", Coalesce(None, None)),
]
for label, value in test_cases:
with self.subTest(case=label):
obj.json_field = value
JSONFieldNullable.objects.bulk_update([obj], fields=["json_field"])
obj.refresh_from_db()
sql_null_qs = JSONFieldNullable.objects.filter(json_field__isnull=True)
self.assertSequenceEqual(sql_null_qs, [obj])
def test_nullable_fk_after_related_save(self): def test_nullable_fk_after_related_save(self):
parent = RelatedObject.objects.create() parent = RelatedObject.objects.create()
child = SingleObject() child = SingleObject()