如何克隆Django模型实例对象并将其保存到数据库?
- 2025-03-06 08:54:00
- admin 原创
- 35
问题描述:
Foo.objects.get(pk="foo")
<Foo: test>
在数据库中,我想添加另一个对象,它是上述对象的副本。
假设我的表有一行。我想将第一行对象插入到具有不同主键的另一行中。我该怎么做?
解决方案 1:
只需改变对象的主键并运行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 数据库查询文档包含有关复制模型实例的部分。假设您的主键是自动生成的,您可以获取要复制的对象,将主键设置为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 推荐的方法。
历史记录:直到 1.4 版,Django 文档才对此进行了解释。不过,在 1.4 版之前,这已经成为可能。
可能的未来功能:上述文档更改已在此工单中进行。在工单的评论线程中,还有一些关于为模型类添加内置copy
函数的讨论,但据我所知,他们决定暂时不解决这个问题。因此,这种“手动”复制方式可能暂时只能这样了。
解决方案 3:
这里要小心。如果你处于某种循环中,并且正在逐个检索对象,那么这可能会非常昂贵。如果你不想调用数据库,只需执行以下操作:
from copy import deepcopy
new_instance = deepcopy(object_you_want_copied)
new_instance.id = None
new_instance.save()
它与其他答案的作用相同,但它不会调用数据库来检索对象。如果你想复制数据库中尚不存在的对象,这也很有用。
解决方案 4:
使用以下代码:
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)
解决方案 5:
这里有一个克隆片段,您可以将其添加到执行此操作的模型中:
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)
解决方案 6:
如何做到这一点已添加到 Django1.4 的官方 Django 文档中
https://docs.djangoproject.com/en/1.10/topics/db/queries/#copying-model-instances
官方答案与 miah 的答案类似,但文档指出了继承和相关对象的一些困难,因此您应该确保阅读文档。
解决方案 7:
我在接受的答案中遇到了一些问题。这是我的解决方案。
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 模型实例的浅表副本。这是我没有找到文档的内容之一,但它通过 pickling 和 unpickling 工作,因此它可能得到了很好的支持。
其次,已批准的答案将保留任何附加到新实例的预取结果。除非您明确复制对多关系,否则这些结果不应与新实例相关联。如果您遍历预取关系,您将获得与数据库不匹配的结果。添加预取时破坏工作代码可能会令人不快。
删除_prefetched_objects_cache
是去除所有预取的快捷方式。后续的对多访问就像从未进行过预取一样。使用以下划线开头的未记录属性可能会带来兼容性问题,但目前它是可行的。
解决方案 8:
这是克隆模型实例的另一种方法:
d = Foo.objects.filter(pk=1).values().first()
d.update({'id': None})
duplicate = Foo.objects.create(**d)
解决方案 9:
最好将 pk 设置为 None,因为 Django 可以正确地为您创建一个 pk
object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()
解决方案 10:
这会在内存中执行复制,您可以独立对其进行修改。
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
解决方案 11:
在Django 5.1中,您应该设置obj.pk=None
和obj._state.adding = True
,然后在docs.djangoproject.com/en/5.1/topics/db/queries/#copying-model-instancesobj.save()
中查看更多信息。
如果您使用模型继承,您也应该设置obj.id = None
。
完整示例:
obj = SomeModel.objects.get(pk=1)
obj.pk = None
obj.id = None
obj._state.adding = True
obj.save() # now obj.pk = 2
解决方案 12:
这个简单的过程对我来说很好:
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()
解决方案 13:
要克隆具有多个继承级别的模型,即> = 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)
请参考此处的问题。
解决方案 14:
尝试一下
original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
解决方案 15:
有一个包可以做到这一点,它在 django 管理站点内创建一个 UI:https://github.com/RealGeeks/django-modelclone
pip install django-modelclone
将“modelclone”添加到 INSTALLED_APPS 并将其导入 admin.py 中。
然后,每当您想要使模型可克隆时,只需将给定管理模型类“modelclone.ClonableModelAdmin”中的“admin.ModelAdmin”替换即可。这会导致给定模型的实例详细信息页面中出现“复制”按钮。
解决方案 16:
如果你有,OneToOneField
那么你应该这样做:
tmp = Foo.objects.get(pk=1)
tmp.pk = None
tmp.id = None
instance = tmp
解决方案 17:
这似乎是最直接和最可靠的 IAW Django 5.1 标准。这里添加的是内部查询以获取对正在复制的对象的新引用。如果您未能做到这一点,则在调用后,copy()
您可能会对同一对象有 2 个引用。
模型类定义内部的这个看起来简单而有效:
def copy(self) -> Self:
dupe = <ModelName>.objects.get(pk=self.pk)
dupe.pk = None
dupe._state.adding = True
dupe.save()
return dupe