Making a copy of a model instance

Copying a model instance, i.e. creating a new database row based on an existing one (copying all the data except the primary key), doesn’t seem to be a very common need – it’s not directly supported by the Django ORM, and Google doesn’t turn much up, either.

But maybe that’s because, in most cases, it really is pretty simple:

>>> obj = Model.objects.get(pk=1)
>>> obj.pk = None    # (or: obj.id=None)
>>> obj.save()
>>> obj.pk
2

That’s how I’ve done it myself in the past. However, it turns out that this doesn’t work when model inheritance is used. When copying a subclass model, obj.id refers to the parent model object’s primary key, and obj.pk points to the parent_link OneToOne relationship. Both need to be nulled for a copy to be created, otherwise the save() logic will cause the missing one to be reset to the original value (I haven’t gone through all of the code, but that seems to be the effect).

>>> obj = SubclassModel.objects.get(pk=1)
>>> obj.pk = None
>>> obj.id = None
>>> obj.save()
>>> obj.pk
2

While that works, I felt like it would be relying on implementation details a bit too much, so currently I am using this function:

from django.db.models import AutoField
def copy_model_instance(obj):
    """Create a copy of a model instance.

    M2M relationships are currently not handled, i.e. they are not
    copied.

    See also Django #4027.
    """
    initial = dict([(f.name, getattr(obj, f.name))
                    for f in obj._meta.fields
                    if not isinstance(f, AutoField) and
                       not f in obj._meta.parents.values()])
    return obj.__class__(**initial)

Which basically initializes a new model instance with the data from the old, while leaving out all id and parent link fields. For example:

>>> obj = SubclassModel.objects.get(pk=1)
>>> obj = copy_model_instance(obj)
>>> obj.save()
>>> obj.pk
>>> 2

So far it works great, and I’m pretty happy with it – I’d love to hear other ideas, though.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s