问题:如何克隆Django模型实例对象并将其保存到数据库?
Foo.objects.get(pk="foo")
<Foo: test>
在数据库中,我想添加另一个对象,它是上述对象的副本。
假设我的桌子有一排。我想用不同的主键将第一行对象插入另一行。我怎样才能做到这一点?
Foo.objects.get(pk="foo")
<Foo: test>
In the database, I want to add another object which is a copy of the object above.
Suppose my table has one row. I want to insert the first row object into another row with a different primary key. How can I do that?
回答 0
回答 1
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
为模型类添加内置函数的问题,但据我所知,他们决定不解决该问题。因此,这种“手动”复制方式现在可能必须要做。
The Django documentation for database queries includes a section on copying model instances. Assuming your primary keys are autogenerated, you get the object you want to copy, set the primary key to None
, and save the object again:
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
blog.save() # blog.pk == 2
In this snippet, the first save()
creates the original object, and the second save()
creates the copy.
If you keep reading the documentation, there are also examples on how to handle two more complex cases: (1) copying an object which is an instance of a model subclass, and (2) also copying related objects, including objects in many-to-many relations.
Note on miah’s answer: Setting the pk to None
is mentioned in miah’s answer, although it’s not presented front and center. So my answer mainly serves to emphasize that method as the Django-recommended way to do it.
Historical note: This wasn’t explained in the Django docs until version 1.4. It has been possible since before 1.4, though.
Possible future functionality: The aforementioned docs change was made in this ticket. On the ticket’s comment thread, there was also some discussion on adding a built-in copy
function for model classes, but as far as I know they decided not to tackle that problem yet. So this “manual” way of copying will probably have to do for now.
回答 2
小心点 如果您处于某种循环中,并且要一个接一个地检索对象,这可能会非常昂贵。如果您不想调用数据库,请执行以下操作:
from copy import deepcopy
new_instance = deepcopy(object_you_want_copied)
new_instance.id = None
new_instance.save()
它与其他一些答案具有相同的作用,但是不会进行数据库调用来检索对象。如果您要复制数据库中尚不存在的对象,这也很有用。
Be careful here. This can be extremely expensive if you’re in a loop of some kind and you’re retrieving objects one by one. If you don’t want the call to the database, just do:
from copy import deepcopy
new_instance = deepcopy(object_you_want_copied)
new_instance.id = None
new_instance.save()
It does the same thing as some of these other answers, but it doesn’t make the database call to retrieve an object. This is also useful if you want to make a copy of an object that doesn’t exist yet in the database.
回答 3
使用以下代码:
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)
Use the below code :
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)
回答 4
有一个克隆片段在这里,你可以添加到您的模型,做到这一点:
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)
There’s a clone snippet here, which you can add to your model which does this:
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)
回答 5
回答 6
我遇到了一些公认的答案。这是我的解决方案。
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
是一种剥离所有预取的快捷方法。随后的许多访问就像从未进行过预取一样工作。使用以下划线开头的未记录属性可能会引起兼容性问题,但现在可以使用。
I’ve run into a couple gotchas with the accepted answer. Here is my solution.
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
Note: this uses solutions that aren’t officially sanctioned in the Django docs, and they may cease to work in future versions. I tested this in 1.9.13.
The first improvement is that it allows you to continue using the original instance, by using copy.copy
. Even if you don’t intend to reuse the instance, it can be safer to do this step if the instance you’re cloning was passed as an argument to a function. If not, the caller will unexpectedly have a different instance when the function returns.
copy.copy
seems to produce a shallow copy of a Django model instance in the desired way. This is one of the things I did not find documented, but it works by pickling and unpickling, so it’s probably well-supported.
Secondly, the approved answer will leave any prefetched results attached to the new instance. Those results shouldn’t be associated with the new instance, unless you explicitly copy the to-many relationships. If you traverse the the prefetched relationships, you will get results that don’t match the database. Breaking working code when you add a prefetch can be a nasty surprise.
Deleting _prefetched_objects_cache
is a quick-and-dirty way to strip away all prefetches. Subsequent to-many accesses work as if there never was a prefetch. Using an undocumented property that begins with an underscore is probably asking for compatibility trouble, but it works for now.
回答 7
将pk设置为None更好,Sinse Django可以为您正确创建一个pk
object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()
setting pk to None is better, sinse Django can correctly create a pk for you
object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()
回答 8
这是克隆模型实例的另一种方法:
d = Foo.objects.filter(pk=1).values().first()
d.update({'id': None})
duplicate = Foo.objects.create(**d)
This is yet another way of cloning the model instance:
d = Foo.objects.filter(pk=1).values().first()
d.update({'id': None})
duplicate = Foo.objects.create(**d)
回答 9
要克隆具有多个继承级别(即> = 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)
请在这里参考问题。
To clone a model with multiple inheritance levels, i.e. >= 2, or ModelC below
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)
Please refer the question here.
回答 10
试试这个
original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
Try this
original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()