【问题标题】:How do I clone a Django model instance object and save it to the database?如何克隆 Django 模型实例对象并将其保存到数据库中?
【发布时间】:2023-03-28 07:49:01
【问题描述】:
Foo.objects.get(pk="foo")
<Foo: test>

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

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

【问题讨论】:

    标签: python django django-models


    【解决方案1】:

    只需更改对象的主键并运行 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 字段,它将改变
    【解决方案2】:

    数据库查询的 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
    

    在这个 sn-p 中,第一个 save() 创建原始对象,第二个 save() 创建副本。

    如果您继续阅读文档,还有一些关于如何处理两个更复杂情况的示例:(1) 复制作为模型子类实例的对象,以及 (2) 还复制相关对象,包括多对多关系。


    关于 miah 的回答的注意事项:miah 的回答中提到了将 pk 设置为 None,尽管它没有显示在前面和中间。所以我的回答主要是为了强调这种方法是 Django 推荐的方法。

    历史记录:直到 1.4 版的 Django 文档中才对此进行了解释。不过,从 1.4 之前就已经有可能了。

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

    【讨论】:

      【解决方案3】:

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

      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 解决方案,但它需要一个额外的步骤,并且与直通关系有相同的问题,无论如何我都必须手动处理,所以它对我没有重大影响。
      【解决方案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)
      

      【讨论】:

      • model_to_dict 采用exclude 参数,这意味着您不需要单独的pop: model_to_dict(instance, exclude=['id'])
      • 这会导致外键异常
      【解决方案5】:

      有一个克隆 sn-p 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 属性。
      【解决方案6】:

      如何做到这一点已添加到 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/…
      【解决方案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 模型实例的浅表副本。这是我没有找到记录的事情之一,但它可以通过酸洗和解酸来工作,因此它可能得到了很好的支持。

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

      删除_prefetched_objects_cache 是一种删除所有预取的快速而肮脏的方法。随后的多次访问就像从来没有预取一样工作。使用以下划线开头的未记录属性可能会导致兼容性问题,但它现在可以使用。

      【讨论】:

      • 我能够让它工作,但它看起来可能在 1.11 中已经改变了,因为我有一个名为 _[model_name]_cache 的属性,一旦删除,我就可以分配一个该相关模型的新 ID,然后调用 save()。仍然可能有我尚未确定的副作用。
      • 如果您在类/mixin 的函数中进行克隆,这是非常重要的信息,否则它会弄乱“自我”,您会感到困惑。
      【解决方案8】:

      设置 pk 为 None 更好,因为 Django 可以正确地为你创建一个 pk

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

      【讨论】:

        【解决方案9】:

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

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

        【讨论】:

          【解决方案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
          

          【讨论】:

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

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

          【讨论】:

          • 啊,是的,但是这个问题没有一个公认的答案!加油!
          【解决方案12】:

          试试这个

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

          【讨论】:

          • 弹出 pk 属性有点无意义。设置为 None 更有意义。
          【解决方案13】:

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

          pip install django-modelclone
          

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

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

          【讨论】:

            【解决方案14】:

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

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

            【讨论】:

              【解决方案15】:

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

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

              【讨论】:

                猜你喜欢
                • 2014-05-11
                • 2013-06-11
                • 2012-02-18
                • 1970-01-01
                • 1970-01-01
                • 2015-09-23
                • 2012-04-13
                • 1970-01-01
                • 2013-09-07
                相关资源
                最近更新 更多