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


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

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

假设我的桌子有一排。我想用不同的主键将第一行对象插入另一行。我怎样才能做到这一点?

Answers:


437

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

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

如果要自动生成密钥,请将新密钥设置为“无”。

有关UPDATE / INSERT的更多信息,请点击这里

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


2
值得一提的是,本文引用了Django 1.2,现在我们使用的是Django 1.4。尚未测试此方法是否有效,但在不确定该方法是否适合您之前不要使用此答案。
2012年

7
在1.4.1中可以正常工作这可能是将持续很长时间的事情之一。
frnhr 2012年

8
我不得不同时设置obj.pkobj.id作出在Django 1.4这项工作
切赫佩勒

3
@PetrPeller- 文档建议这是因为您正在使用模型继承。
Dominic Rodger 2014年

12
注意:如果涉及到外键,涉及到one2one和m2m(即可能存在更复杂的“深度复制”方案),则事情可能会更加复杂
Ben Roberts

135

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的答案None中提到了将pk设置为,尽管未将其设置在前面和中间。因此,我的回答主要是为了强调该方法,这是Django推荐的方法。

历史记录:Django文档直到1.4版才对此进行了解释。但是从1.4之前开始就有可能。

将来可能的功能:对该票证进行了上述文档更改。在故障单的注释线程上,还讨论了copy为模型类添加内置函数的问题,但据我所知,他们决定不解决该问题。因此,这种“手动”复制方式现在可能必须要做。


46

小心点 如果您处于某种循环中,并且要一个接一个地检索对象,这可能会非常昂贵。如果您不想调用数据库,请执行以下操作:

from copy import deepcopy

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

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


1
如果您有一个对象,这将非常有用,您可以在进行更改并保存到新对象之前将其深层复制。然后,您可以进行一些条件检查,并取决于它们是否通过,即对象是否在您要检查的另一个表中,您可以设置new_instance.id = original_instance.id并保存:)谢谢!
radtek

2
如果模型具有多个继承级别,则此方法不起作用。
张大卫

1
就我的情况而言,我想为模型创建一个克隆方法,该方法将使用“ self”变量,而我不能简单地仅将self.pk设置为None,因此该解决方案就像一个魅力。我在下面考虑过model_to_dict解决方案,但是它需要额外的步骤,并且在直通关系上也有同样的问题,无论如何我都必须手动处理,因此对我没有太大影响。
安德森·桑托斯

32

使用以下代码:

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)

8
model_to_dict接受一个exclude参数,这意味着您不需要单独的popmodel_to_dict(instance, exclude=['id'])
georgebrock

20

有一个克隆片段在这里,你可以添加到您的模型,做到这一点:

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)

@ user426975-啊,噢(我已将其从答案中删除了)。
多米尼克·罗杰

不知道这是否是Django版本,但if现在需要是if fld.name != old._meta.pk.name,即实例的name属性_meta.pk
克里斯(Chris

20

如何执行此操作已添加到Django1.4中的官方Django文档中

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

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


当您打开链接时,它说找不到页面
Amrit

Django 1.4不再提供该文档。我将更新答案以指向最新文档。
Michael Bylstra

1
@MichaelBylstra一个好方法,有常绿的链接是使用stable替代的版本号的URL,就像这样:docs.djangoproject.com/en/stable/topics/db/queries/...
Flimm

8

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

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模型实例的浅表副本。这是我未发现的东西之一,但是它可以通过酸洗和酸洗来工作,因此可能得到了很好的支持。

其次,批准的答案将把任何预取结果附加到新实例上。除非您明确复制多对多关系,否则这些结果不应与新实例相关联。如果遍历预取的关系,将得到与数据库不匹配的结果。添加预取时破坏工作代码可能会令人讨厌。

删除_prefetched_objects_cache是一种剥离所有预取的快捷方法。随后的许多访问就像从未进行过预取一样工作。使用以下划线开头的未记录属性可能会引起兼容性问题,但现在可以使用。


我能够使它正常工作,但是好像它在1.11中可能已经更改,因为我有一个名为的属性_[model_name]_cache,该属性一经删除,便能够为该相关模型分配新的ID,然后调用save()。可能还有我尚未确定的副作用。
trpt4him

如果您要在类/ mixin上的函数中进行克隆,这是非常重要的信息,因为否则它会弄乱“ self”,您会感到困惑。
安德烈亚斯·伯格斯特伦

5

将pk设置为None更好,Sinse Django可以为您正确创建一个pk

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

3

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

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

0

要克隆具有多个继承级别(即> = 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)

请在这里参考问题。


是的,但是这个问题没有被接受的答案!要走的路!
Bobort

0

试试这个

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.