克隆继承的 django 模型实例

Clone an inherited django model instance

提问人:Eric 提问时间:4/10/2013 最后编辑:Artem LikhvarEric 更新时间:10/28/2023 访问量:2441

问:

当我克隆一个 django 模型实例时,我曾经清理过“pk”字段。 这似乎不适用于继承的模型:

拿着这个:

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)

现在让我们创建一个实例并通过“通常”的方式克隆它(我使用的是 django shell):

In [1]: c=ModelC(info1="aaa",info2="bbb",info3="ccc")

In [2]: c.save()

In [3]: c.pk
Out[3]: 1L

In [4]: c.pk=None  <------ to clone

In [5]: c.save()   <------ should generate a new instance with a new index key

In [6]: c.pk       
Out[6]: 1L         <------ but don't

In [7]: ModelC.objects.all()
Out[7]: [<ModelC: ModelC object>]   (only one instance !)

我发现的唯一方法是:

In [16]: c.pk =None

In [17]: c.id=None

In [21]: c.modela_ptr_id=None

In [22]: c.modelb_ptr_id=None

In [23]: c.save()

In [24]: c.pk
Out[24]: 2L    <---- successful clone containing info1,info2,info3 from original instance

In [25]: ModelC.objects.all()
Out[25]: [<ModelC: ModelC object>, <ModelC: ModelC object>]

我觉得这很丑陋,有没有更好的方法可以从继承的模型克隆实例?

python django django-models

评论

0赞 Andrew Clark 4/10/2013
您是单独使用基础模型,还是只使用从它们继承的模型?如果不需要单独使用基模型,那么使用抽象基类应该可以工作。
0赞 PepperoniPizza 4/10/2013
实际上,Django 模型中的继承可以被认为是一种不好的做法,你可以使用抽象基类或外键。
0赞 PepperoniPizza 4/10/2013
你能粘贴你的save()方法吗,你如何在模型上设置pk?
0赞 Eric 4/10/2013
实际上,在我的程序中,我有几个继承级别,前 2 个级别是抽象的,然后我还有另外 2 个级别自带参数。我打算再来一个,这就是我做这个例子的原因。当我看到生成的表时,我可能会回到一个更“通常”的模型模式:只有一个非抽象的模型级别。
0赞 James Lin 10/20/2014
我正在使用多态模型,将 basemodel_ptr_id 设置为 None 不会为我创建新记录,除非我将 id 设置为 None 也像您显示的那样有效。

答:

0赞 Max M 8/9/2017 #1
c=ModelC(info1="aaa",info2="bbb",info3="ccc")
# creates an instance

c.save()
# writes instance to db

c.pk=None
# I doubt u can nullify the auto-generated pk of an existing object, because a pk is not nullable
c.save()
# if I'm right nothing will happen here.

所以 c 将永远是同一个对象。如果要克隆它,则需要生成一个新对象。在 ModelC 中使用构造函数:

def __init__(another_modelC_obj=null, self):
   if another_modelC_obj:
      # for every field in another_modelC_obj: do self.field = another_modelC_obj.field
   super().__init__()

所以你可以去

c2=ModelC(c)

或者直接调用它:

c2=ModelC(c.info1, c.info2, c.info3)

那么 c2 和 c 将是相同的,尽管它们的 pk

1赞 Jonah Bishop 9/24/2023 #2

我对 Django 在你假设的场景中的行为感到困惑,所以我在 Django 的 models\base.py 文件中放了一堆语句来弄清楚发生了什么。事实证明,在您设置的继承示例中,实际上具有多个主键。甚至在代码本身中有一个注释引用了这种微妙的行为(参见相关的 Django 票证 17615)。printModelC

问题的关键在于,非抽象模型继承通过关系起作用,关系是 的特殊变体。因此,在您的示例中,最终有两种关系:一种是具体键 (),另一种是通过 ModelB () 继承的键。该属性也可以被视为主键,但我认为它在概念上只是引擎盖下的别名。OneToOneFieldForeignKeyModelCOneToOneFieldmodelb_ptr_idmodela_ptr_ididmodela_ptr_id

当您将实例 pk 值设置为 0 时,您实际上是在清除“第一个”(最接近绑定)主键。但还有另一个主要关键在起作用!Django 例程调用一个内部 _save_parents() 方法,如果键不匹配,该方法会在键之间进行同步。它通过递归地遍历父关系,在进行时同步项目来实现这一点。在初始示例中,此例程是实例主键“重置”为 1 的位置。例程会走到 ,看到它有一个父值(并且父值与当前值不匹配),将两者同步,然后继续。ModelCsave()ModelCModelCModelB

如果我正确理解了代码,则此例程已到位,以确保所有父项都已根据需要写入数据库,以便在保存项目时不会丢失数据。这是微妙的行为,但在继承使用中值得注意。

这是一个非常有趣的问题!

0赞 Ryan Parsa 10/28/2023 #3
  1. 您需要在设置后立即再次使用它。save()c.pk = None

为什么? 因为该字段是自动生成和自动递增的字段。ModelC.id

当你调用 时,Django 会将实例视为新实例,数据库将分配一个唯一的主键值。save()

# old id / pk
print(c.id)

# set it null
c.pk = None

# ask Django to save it again
c.save()

#get new id
print(c.id)
  1. 请注意模型中的其他多对多键或外键,因为这些关系也会被复制。

  2. 如果这是模型的常见要求,请考虑为此目的创建一个抽象类。

from django.db import models

class CloneableModel(models.Model):
    class Meta:
        abstract = True

    def clone(self):
        new_instance = self.__class__().get(id=self.id)
        new_instance.id = None
        new_instance.save()

        return new_instance

class ModelC(CloneableModel):
...