ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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


W
WBC

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

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

如果您想要自动生成的密钥,请将新密钥设置为无。

有关更新/插入 here 的更多信息。

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


在 1.4.1 中运行良好 这可能是那些将继续工作很长时间的事情之一。
我必须同时设置 obj.pkobj.id 才能使其在 Django 1.4 中工作
@PetrPeller - docs 建议这是因为您使用的是模型继承。
注意:如果涉及外键、one2one's 和 m2m's,事情可能会更复杂一些(即,可能会有更复杂的“深拷贝”场景)
如果有 datetime 字段,它将更改
F
Flimm

数据库查询的 Django 文档包括 a section on copying model instances。假设您的主键是自动生成的,您将获得要复制的对象,将主键设置为 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 之前就已经有可能了。

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


t
the_drow

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

from copy import deepcopy

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

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


如果您有一个对象,这非常有用,您可以在进行更改之前对原始对象进行深度复制,对新对象进行更改并保存它。然后您可以进行一些条件检查,并根据它们是否通过,即对象在您正在检查的另一个表中,您可以设置 new_instance.id = original_instance.id 并保存 :) 谢谢!
如果模型具有多个继承级别,这将不起作用。
在我的情况下,我想为模型创建一个克隆方法,该方法将使用“self”变量,我不能简单地将 self.pk 设置为 None,所以这个解决方案就像一个魅力。我考虑了下面的 model_to_dict 解决方案,但它需要一个额外的步骤,并且与直通关系有相同的问题,无论如何我都必须手动处理,所以它对我没有重大影响。
t
t_io

使用以下代码:

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)

model_to_dict 采用 exclude 参数,这意味着您不需要单独的 popmodel_to_dict(instance, exclude=['id'])
这将导致外键异常
D
Dominic Rodger

有一个克隆片段 here,您可以将其添加到执行此操作的模型中:

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,即 _meta.pk 实例的 name 属性。
M
Michael Bylstra

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

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

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


当您打开链接时,它说找不到页面
Django 1.4 的文档不再存在。我将更新答案以指向最新的文档。
@MichaelBylstra 拥有常绿链接的一个好方法是使用 stable 而不是 URL 中的版本号,如下所示:docs.djangoproject.com/en/stable/topics/db/queries/…
m
morningstar

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

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()。仍然可能有我尚未确定的副作用。
如果您在类/mixin 上的函数中进行克隆,这是非常重要的信息,否则它会弄乱“自我”并且您会感到困惑。
A
Ardine

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

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

A
Ahtisham

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

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

W
WhyNotHugo

这会生成一个内存中的副本,您可以独立地对其进行变异。

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

这不会按预期工作,因为它也会复制 pkid,保存克隆将有效地更新克隆的对象。
事实上,克隆将是相同的。如果要将其保存为 new 实例,则只需设置 clone.pk = None。 (如果主键是其他字段,我建议使用 pk 而不是 id,例如:uuid)。
C
Community

克隆具有多个继承级别的模型,即> = 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)

请参考问题here


啊,是的,但是这个问题没有公认的答案!好样的!
P
Pulkit Pahwa

尝试这个

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

弹出 pk 属性有点毫无意义。设置为 None 更有意义。
S
Scott

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

pip install django-modelclone

将“modelclone”添加到 INSTALLED_APPS 并将其导入到 admin.py 中。

然后,每当您想使模型可克隆时,只需在给定的管理模型类“modelclone.ClonableModelAdmin”中替换“admin.ModelAdmin”即可。这会导致该给定模型的实例详细信息页面中出现“复制”按钮。


V
VivienG

如果您有 OneToOneField,那么您应该这样做:

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

M
Mayur Gupta

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

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()

减去此解决方案,在覆盖该方法时无法获得多对多相关