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

How do I clone a Django model instance object and save it to the database?

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

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

假设我的桌子只有一行。我想用不同的主键将第一行对象插入另一行。我该怎么做?


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

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

如果需要自动生成的键,请将新键设置为"无"。

有关更新/插入的详细信息。


用于数据库查询的Django文档包括一个关于复制模型实例的部分。假设您的主键是自动生成的,您将得到要复制的对象,将主键设置为None,然后再次保存该对象:

1
2
3
4
5
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,尽管它没有出现在前面和中间。所以我的答案主要是强调这种方法,作为姜哥推荐的方法。

历史记录:直到1.4版,Django文档才解释这一点。不过,从1.4之前就有可能了。

可能的未来功能:上述文档更改是在此票据中进行的。在Ticket的评论线程中,也有一些关于为模型类添加一个内置的copy函数的讨论,但据我所知,他们还决定不解决这个问题。因此,这种"手动"的复制方式目前可能需要做。


这里要小心。如果你在某个循环中,并且一个接一个地检索对象,那么这会非常昂贵。如果不想调用数据库,只需执行以下操作:

1
2
3
4
5
from copy import deepcopy

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

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


使用以下代码:

1
2
3
4
5
6
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)


这里有一个克隆片段,您可以将其添加到模型中,这样做:

1
2
3
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)


如何做到这一点已添加到Django1.4的官方Django文档中。

https://docs.djangoproject.com/en/1.10/topics/db/queries/复制模型实例

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


将pk设置为none更好,因为django可以为您正确创建pk

1
2
3
object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()

我遇到了一对有着公认答案的人。这是我的解决方案。

1
2
3
4
5
6
7
8
9
10
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
2
3
d = Foo.objects.filter(pk=1).values().first()  
d.update({'id': None})
duplicate = Foo.objects.create(**d)

试试这个

1
2
3
4
5
original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()

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

1
2
3
4
5
6
7
8
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)

请在这里提出问题。