mirror of
https://github.com/django/django.git
synced 2025-05-24 15:56:29 +00:00
Fixed #33322 -- Fixed loss of assigned related object when saving relation with bulk_update().
This commit is contained in:
parent
9ac92b1efc
commit
ed2018037d
@ -931,11 +931,13 @@ class Model(metaclass=ModelBase):
|
|||||||
using=using, raw=raw,
|
using=using, raw=raw,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _prepare_related_fields_for_save(self, operation_name):
|
def _prepare_related_fields_for_save(self, operation_name, fields=None):
|
||||||
# Ensure that a model instance without a PK hasn't been assigned to
|
# Ensure that a model instance without a PK hasn't been assigned to
|
||||||
# a ForeignKey or OneToOneField on this model. If the field is
|
# a ForeignKey or OneToOneField on this model. If the field is
|
||||||
# nullable, allowing the save would result in silent data loss.
|
# nullable, allowing the save would result in silent data loss.
|
||||||
for field in self._meta.concrete_fields:
|
for field in self._meta.concrete_fields:
|
||||||
|
if fields and field not in fields:
|
||||||
|
continue
|
||||||
# If the related field isn't cached, then an instance hasn't been
|
# If the related field isn't cached, then an instance hasn't been
|
||||||
# assigned and there's no need to worry about this check.
|
# assigned and there's no need to worry about this check.
|
||||||
if field.is_relation and field.is_cached(self):
|
if field.is_relation and field.is_cached(self):
|
||||||
|
@ -549,6 +549,8 @@ class QuerySet:
|
|||||||
raise ValueError('bulk_update() cannot be used with primary key fields.')
|
raise ValueError('bulk_update() cannot be used with primary key fields.')
|
||||||
if not objs:
|
if not objs:
|
||||||
return 0
|
return 0
|
||||||
|
for obj in objs:
|
||||||
|
obj._prepare_related_fields_for_save(operation_name='bulk_update', fields=fields)
|
||||||
# PK is used twice in the resulting update query, once in the filter
|
# PK is used twice in the resulting update query, once in the filter
|
||||||
# and once in the WHEN. Each field will also have one CAST.
|
# and once in the WHEN. Each field will also have one CAST.
|
||||||
connection = connections[self.db]
|
connection = connections[self.db]
|
||||||
|
@ -7,7 +7,8 @@ from django.test import TestCase, skipUnlessDBFeature
|
|||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Article, CustomDbColumn, CustomPk, Detail, Individual, JSONFieldNullable,
|
Article, CustomDbColumn, CustomPk, Detail, Individual, JSONFieldNullable,
|
||||||
Member, Note, Number, Order, Paragraph, SpecialCategory, Tag, Valid,
|
Member, Note, Number, Order, Paragraph, RelatedObject, SingleObject,
|
||||||
|
SpecialCategory, Tag, Valid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -250,3 +251,32 @@ class BulkUpdateTests(TestCase):
|
|||||||
obj.json_field = {'c': obj.json_field['a'] + 1}
|
obj.json_field = {'c': obj.json_field['a'] + 1}
|
||||||
JSONFieldNullable.objects.bulk_update(objs, ['json_field'])
|
JSONFieldNullable.objects.bulk_update(objs, ['json_field'])
|
||||||
self.assertCountEqual(JSONFieldNullable.objects.filter(json_field__has_key='c'), objs)
|
self.assertCountEqual(JSONFieldNullable.objects.filter(json_field__has_key='c'), objs)
|
||||||
|
|
||||||
|
def test_nullable_fk_after_related_save(self):
|
||||||
|
parent = RelatedObject.objects.create()
|
||||||
|
child = SingleObject()
|
||||||
|
parent.single = child
|
||||||
|
parent.single.save()
|
||||||
|
RelatedObject.objects.bulk_update([parent], fields=['single'])
|
||||||
|
self.assertEqual(parent.single_id, parent.single.pk)
|
||||||
|
parent.refresh_from_db()
|
||||||
|
self.assertEqual(parent.single, child)
|
||||||
|
|
||||||
|
def test_unsaved_parent(self):
|
||||||
|
parent = RelatedObject.objects.create()
|
||||||
|
parent.single = SingleObject()
|
||||||
|
msg = (
|
||||||
|
"bulk_update() prohibited to prevent data loss due to unsaved "
|
||||||
|
"related object 'single'."
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
RelatedObject.objects.bulk_update([parent], fields=['single'])
|
||||||
|
|
||||||
|
def test_unspecified_unsaved_parent(self):
|
||||||
|
parent = RelatedObject.objects.create()
|
||||||
|
parent.single = SingleObject()
|
||||||
|
parent.f = 42
|
||||||
|
RelatedObject.objects.bulk_update([parent], fields=['f'])
|
||||||
|
parent.refresh_from_db()
|
||||||
|
self.assertEqual(parent.f, 42)
|
||||||
|
self.assertIsNone(parent.single)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user