复制Django模型实例及其所有指向它的外键
复制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()
(按照我的命名方案)将为空。
问题的出现原因:
- 在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
以上是一个更通用的方法,可以处理一对多和多对多的相关字段。
在这段代码中,作者定义了一个名为"Duplicate"的方法,用于复制一个Django模型实例,并复制所有指向该实例的外键。作者首先列出了要复制的外键对象的列表,然后重置了原实例的主键,创建了一个新的实例,并保存到数据库中。接下来,作者创建了一个字典,用于保存每个外键对象的类和实例。然后,作者使用bulk_create方法一次性将所有外键对象保存到数据库中。
这段代码的目的是为了解决如何复制一个Django模型实例并复制所有指向它的外键的问题。通过使用上述的duplicate方法,可以轻松地复制一个模型实例,并复制所有指向它的外键。
总结起来,这段代码的核心思想是通过重置主键来创建一个新的模型实例,并通过使用bulk_create方法一次性保存所有的外键对象。这样做可以避免多次访问数据库,提高效率。
原文链接:https://stackoverflow.com/questions/10400830
问题的出现的原因是:需要对一个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模型实例的复制,并复制所有指向它的外键。