python - 在 Django/Algorithm 中复制模型实例及其相关对象以递归地复制对象

标签 python django django-models duplicates

我有 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 支持开箱即用的方式吗?复制模型的通用算法是什么样的?

干杯,

约翰


更新:

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

最佳答案

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

I posted my solution on 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

关于python - 在 Django/Algorithm 中复制模型实例及其相关对象以递归地复制对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/437166/

相关文章:

python和mysql将数据下载到csv中

python - Django - 如何在 format_html 安全字符串中使用 URL 模板标记?

Django 新手 ManyRelated Manager 不可迭代的问题

python - Django:模型之间的引用

Django:通过自定义属性过滤 foo_set?

python - 从 Pandas 中删除重复列读取 excel 数据框

Python后台shell脚本通信

python - 类型错误 : cannot concatenate 'str' and 'function' objects python files

django - 在 aws elastic beanstalk 上运行 cron 作业 - django

Django:密码输入为空