mirror of
https://github.com/django/django.git
synced 2025-06-08 13:09:13 +00:00
Fixed #33312 -- Raised explicit exception when copying deferred model instances.
Previously save() would crash with an attempted forced update message, and both save(force_insert=True) and bulk_create() would crash with DoesNotExist errors trying to retrieve rows with an empty primary key (id IS NULL). Implementing deferred field model instance copying might be doable in certain cases (e.g. when all the deferred fields are db generated) but that's not trivial to implement in a backward compatible way. Thanks Adam Sołtysik for the report and test and Clifford for the review.
This commit is contained in:
parent
0b2ed4f7c8
commit
e03e5c751c
@ -859,6 +859,7 @@ class Model(AltersData, metaclass=ModelBase):
|
||||
not force_insert
|
||||
and deferred_non_generated_fields
|
||||
and using == self._state.db
|
||||
and self._is_pk_set()
|
||||
):
|
||||
field_names = set()
|
||||
pk_fields = self._meta.pk_fields
|
||||
|
@ -220,9 +220,10 @@ class DeferredAttribute:
|
||||
# might be able to reuse the already loaded value. Refs #18343.
|
||||
val = self._check_parent_chain(instance)
|
||||
if val is None:
|
||||
if not instance._is_pk_set() and self.field.generated:
|
||||
if not instance._is_pk_set():
|
||||
raise AttributeError(
|
||||
"Cannot read a generated field from an unsaved model."
|
||||
f"Cannot retrieve deferred field {field_name!r} "
|
||||
"from an unsaved model."
|
||||
)
|
||||
instance.refresh_from_db(fields=[field_name])
|
||||
else:
|
||||
|
@ -1590,6 +1590,21 @@ For example, assuming ``entry`` is already duplicated as above::
|
||||
detail.entry = entry
|
||||
detail.save()
|
||||
|
||||
Note that it is not possible to copy instances of models with deferred fields
|
||||
using this pattern unless values are assigned to them:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> blog = Blog.objects.defer("name")[0]
|
||||
>>> blog.pk = None
|
||||
>>> blog._state.adding = True
|
||||
>>> blog.save()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: Cannot retrieve deferred field 'name' from an unsaved model.
|
||||
>>> blog.name = "Another Blog"
|
||||
>>> blog.save()
|
||||
|
||||
.. _topics-db-queries-update:
|
||||
|
||||
Updating multiple objects at once
|
||||
|
@ -366,3 +366,25 @@ class DeferDeletionSignalsTests(TestCase):
|
||||
Proxy.objects.only("value").get(pk=self.item_pk).delete()
|
||||
self.assertEqual(self.pre_delete_senders, [Proxy])
|
||||
self.assertEqual(self.post_delete_senders, [Proxy])
|
||||
|
||||
|
||||
class DeferCopyInstanceTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
SimpleItem.objects.create(name="test", value=42)
|
||||
cls.deferred_item = SimpleItem.objects.defer("value").first()
|
||||
cls.deferred_item.pk = None
|
||||
cls.deferred_item._state.adding = True
|
||||
cls.expected_msg = (
|
||||
"Cannot retrieve deferred field 'value' from an unsaved model."
|
||||
)
|
||||
|
||||
def test_save(self):
|
||||
with self.assertRaisesMessage(AttributeError, self.expected_msg):
|
||||
self.deferred_item.save(force_insert=True)
|
||||
with self.assertRaisesMessage(AttributeError, self.expected_msg):
|
||||
self.deferred_item.save()
|
||||
|
||||
def test_bulk_create(self):
|
||||
with self.assertRaisesMessage(AttributeError, self.expected_msg):
|
||||
SimpleItem.objects.bulk_create([self.deferred_item])
|
||||
|
@ -180,7 +180,7 @@ class GeneratedFieldTestMixin:
|
||||
|
||||
def test_unsaved_error(self):
|
||||
m = self.base_model(a=1, b=2)
|
||||
msg = "Cannot read a generated field from an unsaved model."
|
||||
msg = "Cannot retrieve deferred field 'field' from an unsaved model."
|
||||
with self.assertRaisesMessage(AttributeError, msg):
|
||||
m.field
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user