复制Django模型实例及其所有指向它的外键

23 浏览
0 Comments

复制Django模型实例及其所有指向它的外键

我想在Django模型上创建一个方法,称为model.duplicate(),它可以复制模型实例,包括所有指向它的外键。我知道可以这样做:

def duplicate(self):
   self.pk = None
   self.save()

...但是这样做后,所有相关的模型仍然指向旧实例。

我不能简单地保存对原始对象的引用,因为在方法执行期间self指向会发生变化:

def duplicate(self):
    original = self
    self.pk = None
    self.save()
    assert original is not self    # 失败

我可以尝试仅保存与相关对象的引用:

def duplicate(self):
    original_fkeys = self.fkeys.all()
    self.pk = None
    self.save()
    self.fkeys.add(*original_fkeys)

...但这会将它们从原始记录转移到新记录中。我需要将它们复制并指向新记录。

在其他地方(以及在我更新问题之前的这里)有几个答案建议使用Python的copy,我怀疑这适用于该模型上的外键,但不适用于指向它的其他模型的外键。

def duplicate(self):
    new_model = copy.deepcopy(self)
    new_model.pk = None
    new_model.save()

如果这样做,new_model.fkeys.all()(按照我的命名方案)将为空。

0
0 Comments

问题的出现原因:

- 在Django 2.1和Python 3.6中,尝试了其他答案,但它们似乎没有复制一对多和多对多的相关对象。

- self._meta.fields不包括一对多关联字段,但self._meta.get_fields()包括。

- 其他答案需要先知道相关字段的名称或哪些外键需要复制。

解决方法:

- 根据Django文档,有三个步骤需要依次进行:

1. 枚举相关的子对象和多对多关系,并保存在列表/字典中。

2. 根据Django文档复制父对象(不复制关系)。

3a. 复制子对象,与复制的父对象建立关系。

3b. 在复制的父对象上重新创建多对多关系。

下面是具体实现的代码:

def duplicate_object(self):
    related_objects_to_copy = []
    relations_to_set = {}
    
    for field in self._meta.get_fields():
        if field.one_to_many:
            related_object_manager = getattr(self, field.name)
            related_objects = list(related_object_manager.all())
            if related_objects:
                related_objects_to_copy += related_objects
        elif field.many_to_one:
            pass
        elif field.many_to_many:
            related_object_manager = getattr(self, field.name)
            relations = list(related_object_manager.all())
            if relations:
                relations_to_set[field.name] = relations
    
    self.pk = None
    self.save()
    
    for related_object in related_objects_to_copy:
        for related_object_field in related_object._meta.fields:
            if related_object_field.related_model == self.__class__:
                related_object.pk = None
                setattr(related_object, related_object_field.name, self)
                related_object.save()
    
    for field_name, relations in relations_to_set.items():
        field = getattr(self, field_name)
        field.set(relations)
    
    return self

以上是一个更通用的方法,可以处理一对多和多对多的相关字段。

0
0 Comments

在这段代码中,作者定义了一个名为"Duplicate"的方法,用于复制一个Django模型实例,并复制所有指向该实例的外键。作者首先列出了要复制的外键对象的列表,然后重置了原实例的主键,创建了一个新的实例,并保存到数据库中。接下来,作者创建了一个字典,用于保存每个外键对象的类和实例。然后,作者使用bulk_create方法一次性将所有外键对象保存到数据库中。

这段代码的目的是为了解决如何复制一个Django模型实例并复制所有指向它的外键的问题。通过使用上述的duplicate方法,可以轻松地复制一个模型实例,并复制所有指向它的外键。

总结起来,这段代码的核心思想是通过重置主键来创建一个新的模型实例,并通过使用bulk_create方法一次性保存所有的外键对象。这样做可以避免多次访问数据库,提高效率。

原文链接:https://stackoverflow.com/questions/10400830

0
0 Comments

问题的出现的原因是:需要对一个Django模型实例进行复制,并且需要复制所有指向它的外键。

解决方法如下:

1. 首先,创建一个新的实例并保存它。

2. 然后,遍历所有的外键字段,将它们的值复制给新的实例,并将id字段移除。

3. 创建一个新的外键实例列表。

4. 遍历原始外键实例列表,将每个外键实例的字段值复制给新的外键实例,并将id字段移除。同时将foreign_key_field字段设置为新实例的id。

5. 将新的外键实例列表使用bulk_create方法批量创建。

6. 返回新的实例。

上述解决方法中的代码如下:

def duplicate(self):
    kwargs = {}
    for field in self._meta.fields:
        kwargs[field.name] = getattr(self, field.name)
    kwargs.pop('id')
    new_instance = self.__class__(**kwargs)
    new_instance.save()
    
    fkeys_qs = self.fkeys.all()
    new_fkeys = []
    for fkey in fkey_qs:
        fkey_kwargs = {}
        for field in fkey._meta.fields:
            fkey_kwargs[field.name] = getattr(fkey, field.name)
        fkey_kwargs.pop('id')
        fkey_kwargs['foreign_key_field'] = new_instance.id
        new_fkeys.append(fkey_qs.model(**fkey_kwargs))
    
    fkeys_qs.model.objects.bulk_create(new_fkeys)
    return new_instance

该解决方法适用于简单字段,对于ManyToMany字段的处理方式不确定。可以通过使用pop方法来排除不需要复制的字段。

在解决方法中的关键部分是使用新的id来设置foreign_key_field字段,并且使用bulk_create方法来批量创建新的外键实例,以减少数据库操作次数。

这个解决方法是在之前的解决方案基础上进行修改的,它解决了外键指向模型的问题。

对于性能方面的考虑,可以使用bulk_create方法来一次性创建多个外键实例,而不是在循环中调用save方法。

总之,通过以上的解决方法,可以实现对Django模型实例的复制,并复制所有指向它的外键。

0