Opened 4 years ago

Closed 4 months ago

#33312 closed Cleanup/optimization (fixed)

Instances with deferred fields cannot be used for copying.

Reported by: Adam Sołtysik Owned by: Simon Charette
Component: Database layer (models, ORM) Version: 3.2
Severity: Normal Keywords:
Cc: Egor R Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

As per documentation, it's possible to clone objects by setting pk = None.

However, if the object to clone comes from a deferred queryset (after calling defer or only), trying to save the copy raises some hard to decipher exceptions.

class DeferCloneTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        SimpleItem.objects.create(name="test", value=42)

    def _get_item(self):
        item = SimpleItem.objects.defer('value').first()  # same with `only` instead of `defer`
        item.pk = None
        item._state.adding = True  # doesn't seem to change anything
        return item

    def test_save(self):
        self._get_item().save()  # ValueError: Cannot force an update in save() with no primary key.

    def test_save_force_insert(self):
        self._get_item().save(force_insert=True)  # SimpleItem.DoesNotExist

    def test_bulk_create(self):
        SimpleItem.objects.bulk_create([self._get_item()])  # SimpleItem.DoesNotExist

Possibly related: #27419, #28019.

Change History (8)

comment:1 by Egor R, 4 years ago

Cc: Egor R added

comment:2 by Mariusz Felisiak, 4 years ago

Summary: Errors when trying to clone an object from a deferred querysetInstances with deferred fields cannot be used for copying.
Triage Stage: UnreviewedAccepted
Type: BugCleanup/optimization

Thanks for the report. Instances with deferred fields cannot be used for copying model instances, because "When calling save() for instances with deferred fields, only the loaded fields will be saved" (see docs). It's niche and I don't think it's worth changing, or even if it's feasible with backward compatibility, we can add a warning to the "Copying model instances" section, e.g. Instances with deferred fields cannot be used .....

comment:3 by Adam Sołtysik, 4 years ago

Ok, I understand that it might not make much sense to implement copying instances with deferred fields. A warning in the docs would certainly be helpful. Adding a more readable exception message (consistent for all the cases) may also be worth considering, as sometimes it's not obvious that our queryset has deferred fields, like when there's a custom manager defined for a model. Especially the DoesNotExist exception in the last two cases in very unclear.

comment:4 by Kuldeep Pisda, 4 years ago

Owner: changed from nobody to Kuldeep Pisda
Status: newassigned

comment:5 by Simon Charette, 4 months ago

Owner: changed from Kuldeep Pisda to Simon Charette

comment:6 by Simon Charette, 4 months ago

Has patch: set

comment:7 by Clifford Gama, 4 months ago

Triage Stage: AcceptedReady for checkin

comment:8 by Sarah Boyce <42296566+sarahboyce@…>, 4 months ago

Resolution: fixed
Status: assignedclosed

In e03e5c75:

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.

Note: See TracTickets for help on using tickets.
Back to Top