如何克隆 Django 模型实例对象并将其保存到数据库中?

Foo.objects.get(pk="foo")
<Foo: test>

在数据库中,我想添加另一个对象,它是上述对象的副本。

假设我的表只有一行。我想将第一行对象插入到另一行中,并使用不同的主键。我该怎么做?

187965 次浏览

有一个克隆片段在这里,你可以把它添加到你的模型中,这样做:

def clone(self):
new_kwargs = dict([(fld.name, getattr(old, fld.name)) for fld in old._meta.fields if fld.name != old._meta.pk]);
return self.__class__.objects.create(**new_kwargs)

只需更改对象的主键并运行save()。

obj = Foo.objects.get(pk=<some_existing_pk>)
obj.pk = None
obj.save()

如果需要自动生成密钥,请将新密钥设置为None。

关于UPDATE/INSERT 在这里的更多信息。

关于复制模型实例的官方文档:https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances

Django数据库查询文档包括关于复制模型实例的一节。假设你的主键是自动生成的,你得到你想要复制的对象,设置主键为None,并再次保存该对象:

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1


blog.pk = None
blog.save() # blog.pk == 2

在这个代码片段中,第一个save()创建原始对象,第二个save()创建副本。

如果你继续阅读文档,还有关于如何处理两种更复杂情况的示例:(1)复制作为模型子类实例的对象,以及(2)复制相关对象,包括多对多关系的对象。


注意miah的回答:在miah的回答中提到了将pk设置为None,尽管它没有显示在前面和中心。所以我的回答主要是为了强调这个方法是django推荐的方法。

历史提示:Django文档直到1.4版才解释了这一点。不过,在1.4之前就已经实现了。

未来可能的功能:前面提到的文档更改是在这张票中进行的。在门票的评论线程中,也有一些关于为模型类添加内置copy函数的讨论,但据我所知,他们决定暂时不解决这个问题。所以这种“手动”的复制方式现在可能只能用了。

如何做到这一点被添加到Django官方文档在Django1.4

https://docs.djangoproject.com/en/1.10/topics/db/queries/#copying-model-instances

官方的答案与miah的答案相似,但是文档指出了继承和相关对象的一些困难,所以您应该确保阅读了文档。

这里要小心。如果您处于某种类型的循环中,并且逐个检索对象,那么这可能会非常昂贵。如果你不想调用数据库,只需执行:

from copy import deepcopy


new_instance = deepcopy(object_you_want_copied)
new_instance.id = None
new_instance.save()

它做的事情与其他一些答案相同,但它不进行数据库调用来检索对象。如果您想要复制数据库中还不存在的对象,这也很有用。

使用下面的代码:

from django.forms import model_to_dict


instance = Some.objects.get(slug='something')


kwargs = model_to_dict(instance, exclude=['id'])
new_instance = Some.objects.create(**kwargs)

设置pk为None更好,因为Django可以正确地为你创建一个pk

object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()

克隆具有多个继承级别的模型,即>= 2,或ModelC以下

class ModelA(models.Model):
info1 = models.CharField(max_length=64)


class ModelB(ModelA):
info2 = models.CharField(max_length=64)


class ModelC(ModelB):
info3 = models.CharField(max_length=64)

请参考问题在这里

试试这个

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()

我在公认的答案中遇到了一些问题。这是我的解决方案。

import copy


def clone(instance):
cloned = copy.copy(instance) # don't alter original instance
cloned.pk = None
try:
delattr(cloned, '_prefetched_objects_cache')
except AttributeError:
pass
return cloned

注意:这里使用的解决方案并没有在Django文档中得到官方认可,而且这些解决方案在未来的版本中可能会失效。我在1.9.13测试了这个功能。

第一个改进是它允许你通过copy.copy继续使用原来的实例。即使您不打算重用实例,如果您要克隆的实例是作为参数传递给函数的,那么执行这一步会更安全。否则,当函数返回时,调用者将意外地拥有一个不同的实例。

copy.copy似乎以所需的方式生成了一个Django模型实例的浅拷贝。这是我没有找到文档的东西之一,但它通过pickle和unpickle工作,因此可能得到了良好的支持。

其次,已批准的答案将把任何预取的结果附加到新实例。这些结果不应该与新实例相关联,除非显式地复制“多到多”关系。如果遍历预取的关系,将得到与数据库不匹配的结果。当您添加预取时,破坏工作代码可能是一个令人讨厌的惊喜。

删除_prefetched_objects_cache是去除所有预取的快速而肮脏的方法。后续的多路访问就像从来没有预取一样。使用以下划线开头的未记录的属性可能会带来兼容性问题,但目前是可行的。

这是克隆模型实例的另一种方式:

d = Foo.objects.filter(pk=1).values().first()
d.update({'id': None})
duplicate = Foo.objects.create(**d)

这将在内存中进行复制,您可以独立地进行突变。

original = CheckoutItem(title="test", ...)
copy = CheckoutItem()


for f in CheckoutItem._meta.fields:
setattr(copy, f.attname, getattr(original, f.attname))

或者,作为一种方法:


def clone(self):
"""Returns a clone of this instance."""


clone = self.__class__()
for f in self.__class__._meta.fields:
setattr(clone, f.attname, getattr(self, f.attname))


return clone

有一个包可以做到这一点,它在django管理站点中创建一个UI: https://github.com/RealGeeks/django-modelclone

pip install django-modelclone

添加“modelclone"到INSTALLED_APPS,并在admin.py中导入它。

然后,每当你想让一个模型可克隆时,你只需替换“;admin.ModelAdmin"在给定的管理模型类“modelclone. clonablemodeladmin”中。这就导致了“重复”;出现在给定模型的实例详细信息页面中的按钮。

如果你有一个OneToOneField,那么你应该这样做:

    tmp = Foo.objects.get(pk=1)
tmp.pk = None
tmp.id = None
instance = tmp

这个简单的过程对我来说很有效:

foo_obj = Foo.objects.get(pk="foo")
foo_values = foo_obj.__dict__
foo_values.pop('_state')
foo_values.pop('id')
foo_new_obj = Foo(**foo_values)
foo_new_obj.save()