I want to create a method on a Django model, call it model.duplicate()
, that duplicates the model instance, including all the foreign keys pointing to it. I kno
Although I accepted the other poster's answer (since it helped me get here), I wanted to post the solution I ended up with in case it helps someone else stuck in the same place.
def duplicate(self):
"""
Duplicate a model instance, making copies of all foreign keys pointing
to it. This is an in-place method in the sense that the record the
instance is pointing to will change once the method has run. The old
record is still accessible but must be retrieved again from
the database.
"""
# I had a known set of related objects I wanted to carry over, so I
# listed them explicitly rather than looping over obj._meta.fields
fks_to_copy = list(self.fkeys_a.all()) + list(self.fkeys_b.all())
# Now we can make the new record
self.pk = None
# Make any changes you like to the new instance here, then
self.save()
foreign_keys = {}
for fk in fks_to_copy:
fk.pk = None
# Likewise make any changes to the related model here
# However, we avoid calling fk.save() here to prevent
# hitting the database once per iteration of this loop
try:
# Use fk.__class__ here to avoid hard-coding the class name
foreign_keys[fk.__class__].append(fk)
except KeyError:
foreign_keys[fk.__class__] = [fk]
# Now we can issue just two calls to bulk_create,
# one for fkeys_a and one for fkeys_b
for cls, list_of_fks in foreign_keys.items():
cls.objects.bulk_create(list_of_fks)
What it looks like when you use it:
In [6]: model.id
Out[6]: 4443
In [7]: model.duplicate()
In [8]: model.id
Out[8]: 17982
In [9]: old_model = Model.objects.get(id=4443)
In [10]: old_model.fkeys_a.count()
Out[10]: 2
In [11]: old_model.fkeys_b.count()
Out[11]: 1
In [12]: model.fkeys_a.count()
Out[12]: 2
In [13]: model.fkeys_b.count()
Out[13]: 1
Model and related_model names changed to protect the innocent.