在 Django 中复制模型实例及其相关对象/递归复制对象的算法

新手上路,请多包涵

我有模型 BooksChaptersPages 。它们都是由 User 编写的:

 from django.db import models

class Book(models.Model)
    author = models.ForeignKey('auth.User')

class Chapter(models.Model)
    author = models.ForeignKey('auth.User')
    book = models.ForeignKey(Book)

class Page(models.Model)
    author = models.ForeignKey('auth.User')
    book = models.ForeignKey(Book)
    chapter = models.ForeignKey(Chapter)

我想做的是复制现有的 Book 并将其更新为 User 给其他人。皱纹是我还想将所有相关模型实例复制到 Book - 都是 ChaptersPages

当查看 Page 时,事情变得非常棘手——不仅新的 Pages 需要让他们的 author 字段更新新 Chapter 对象!

Django 是否支持开箱即用的方式来做到这一点?复制模型的通用算法是什么样的?

干杯,

约翰


更新:

上面给出的类只是一个例子来说明我遇到的问题!

原文由 jb. 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 492
2 个回答

这在 Django 1.3 中不再有效,因为 CollectedObjects 已被删除。参见 变更集 14507

我在 Django Snippets 上发布了我的解决方案。 它主要基于 django.db.models.query.CollectedObject 用于删除对象的代码:

 from django.db.models.query import CollectedObjects
from django.db.models.fields.related import ForeignKey

def duplicate(obj, value, field):
    """
    Duplicate all related objects of `obj` setting
    `field` to `value`. If one of the duplicate
    objects has an FK to another duplicate object
    update that as well. Return the duplicate copy
    of `obj`.
    """
    collected_objs = CollectedObjects()
    obj._collect_sub_objects(collected_objs)
    related_models = collected_objs.keys()
    root_obj = None
    # Traverse the related models in reverse deletion order.
    for model in reversed(related_models):
        # Find all FKs on `model` that point to a `related_model`.
        fks = []
        for f in model._meta.fields:
            if isinstance(f, ForeignKey) and f.rel.to in related_models:
                fks.append(f)
        # Replace each `sub_obj` with a duplicate.
        sub_obj = collected_objs[model]
        for pk_val, obj in sub_obj.iteritems():
            for fk in fks:
                fk_value = getattr(obj, "%s_id" % fk.name)
                # If this FK has been duplicated then point to the duplicate.
                if fk_value in collected_objs[fk.rel.to]:
                    dupe_obj = collected_objs[fk.rel.to][fk_value]
                    setattr(obj, fk.name, dupe_obj)
            # Duplicate the object and save it.
            obj.id = None
            setattr(obj, field, value)
            obj.save()
            if root_obj is None:
                root_obj = obj
    return root_obj

对于 django >= 2 应该有一些最小的变化。所以输出将是这样的:

 def duplicate(obj, value=None, field=None, duplicate_order=None):
    """
    Duplicate all related objects of obj setting
    field to value. If one of the duplicate
    objects has an FK to another duplicate object
    update that as well. Return the duplicate copy
    of obj.
    duplicate_order is a list of models which specify how
    the duplicate objects are saved. For complex objects
    this can matter. Check to save if objects are being
    saved correctly and if not just pass in related objects
    in the order that they should be saved.
    """
    from django.db.models.deletion import Collector
    from django.db.models.fields.related import ForeignKey

    collector = Collector(using='default')
    collector.collect([obj])
    collector.sort()
    related_models = collector.data.keys()
    data_snapshot = {}
    for key in collector.data.keys():
        data_snapshot.update(
            {key: dict(zip([item.pk for item in collector.data[key]], [item for item in collector.data[key]]))})
    root_obj = None

    # Sometimes it's good enough just to save in reverse deletion order.
    if duplicate_order is None:
        duplicate_order = reversed(related_models)

    for model in duplicate_order:
        # Find all FKs on model that point to a related_model.
        fks = []
        for f in model._meta.fields:
            if isinstance(f, ForeignKey) and f.remote_field.related_model in related_models:
                fks.append(f)
        # Replace each `sub_obj` with a duplicate.
        if model not in collector.data:
            continue
        sub_objects = collector.data[model]
        for obj in sub_objects:
            for fk in fks:
                fk_value = getattr(obj, "%s_id" % fk.name)
                # If this FK has been duplicated then point to the duplicate.
                fk_rel_to = data_snapshot[fk.remote_field.related_model]
                if fk_value in fk_rel_to:
                    dupe_obj = fk_rel_to[fk_value]
                    setattr(obj, fk.name, dupe_obj)
            # Duplicate the object and save it.
            obj.id = None
            if field is not None:
                setattr(obj, field, value)
            obj.save()
            if root_obj is None:
                root_obj = obj
    return root_obj

原文由 jb. 发布,翻译遵循 CC BY-SA 4.0 许可协议

这是复制对象的简单方法。

基本上:

(1) 将原始对象的 id 设置为无:

book_to_copy.id = 无

(2) 更改“作者”属性并保存对象:

book_to_copy.author = 新作者

book_to_copy.save()

(3) 执行 INSERT 而不是 UPDATE

(它没有解决在页面中更改作者的问题——我同意关于重构模型的评论)

原文由 rprasad 发布,翻译遵循 CC BY-SA 2.5 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题