Django 克隆递归对象

2023-12-24

以前,当我想递归地克隆对象时,我遇到了问题。 我知道克隆对象的简单方法是这样的:

obj = Foo.objects.get(pk=<some_existing_pk>)
obj.pk = None
obj.save()

但是,我想做得更深入。例如,我有一个models.py

class Post(TimeStampedModel):
    author = models.ForeignKey(User, related_name='posts',
                               on_delete=models.CASCADE)
    title = models.CharField(_('Title'), max_length=200)
    content = models.TextField(_('Content'))

    ...


class Comment(TimeStampedModel):
    author = models.ForeignKey(User, related_name='comments',
                               on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    comment = models.TextField(_('Comment'))

    ...


class CommentAttribute(TimeStampedModel):
    comment = models.OneToOneField(Comment, related_name='comment_attribute',
                                   on_delete=models.CASCADE)
    is_bookmark = models.BooleanField(default=False)

    ...


class PostComment(TimeStampedModel):
    post = models.ForeignKey(Post, related_name='post_comments',
                             on_delete=models.CASCADE)
    comments = models.ManyToManyField(Comment)

    ...

当我从以下位置克隆父对象时Post,孩子的对象就像Comment, CommentAttribute and PostComment也将通过以下新克隆来克隆Post对象。 子模型是动态地。因此,我想通过创建对象克隆器之类的工具来使其变得简单。

下面的代码片段是我所做的;

from django.db.utils import IntegrityError


class ObjectCloner(object):
    """
    [1]. The simple way with global configuration:
    >>> cloner = ObjectCloner()
    >>> cloner.set_objects = [obj1, obj2]   # or can be queryset
    >>> cloner.include_childs = True
    >>> cloner.max_clones = 1
    >>> cloner.execute()

    [2]. Clone the objects with custom configuration per-each objects.
    >>> cloner = ObjectCloner()
    >>> cloner.set_objects = [
        {
            'object': obj1,
            'include_childs': True,
            'max_clones': 2
        },
        {
            'object': obj2,
            'include_childs': False,
            'max_clones': 1
        }
    ]
    >>> cloner.execute()
    """
    set_objects = []            # list/queryset of objects to clone.
    include_childs = True       # include all their childs or not.
    max_clones = 1              # maximum clone per-objects.

    def clone_object(self, object):
        """
        function to clone the object.
        :param `object` is an object to clone, e.g: <Post: object(1)>
        :return new object.
        """
        try:
            object.pk = None
            object.save()
            return object
        except IntegrityError:
            return None

    def clone_childs(self, object):
        """
        function to clone all childs of current `object`.
        :param `object` is a cloned parent object, e.g: <Post: object(1)>
        :return
        """
        # bypass the none object.
        if object is None:
            return

        # find the related objects contains with this current object.
        # e.g: (<ManyToOneRel: app.comment>,)
        related_objects = object._meta.related_objects

        if len(related_objects) > 0:
            for relation in related_objects:
                # find the related field name in the child object, e.g: 'post'
                remote_field_name = relation.remote_field.name

                # find all childs who have the same parent.
                # e.g: childs = Comment.objects.filter(post=object)
                childs = relation.related_model.objects.all()

                for old_child in childs:
                    new_child = self.clone_object(old_child)

                    if new_child is not None:
                        # FIXME: When the child field as M2M field, we gote this error.
                        # "TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use comments.set() instead."
                        # how can I clone that M2M values?
                        setattr(new_child, remote_field_name, object)
                        new_child.save()

                    self.clone_childs(new_child)
        return

    def execute(self):
        include_childs = self.include_childs
        max_clones = self.max_clones
        new_objects = []

        for old_object in self.set_objects:
            # custom per-each objects by using dict {}.
            if isinstance(old_object, dict):
                include_childs = old_object.get('include_childs', True)
                max_clones = old_object.get('max_clones', 1)
                old_object = old_object.get('object')  # assigned as object or None.

            for _ in range(max_clones):
                new_object = self.clone_object(old_object)
                if new_object is not None:
                    if include_childs:
                        self.clone_childs(new_object)
                    new_objects.append(new_object)

        return new_objects

但是,问题是当子字段作为 M2M 字段时,我们收到此错误。

>>> cloner.set_objects = [post]
>>> cloner.execute()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/agus/envs/env-django-cloner/django-object-cloner/object_cloner_demo/app/utils.py", line 114, in execute
    self.clone_childs(new_object)
  File "/home/agus/envs/env-django-cloner/django-object-cloner/object_cloner_demo/app/utils.py", line 79, in clone_childs
    self.clone_childs(new_child)
  File "/home/agus/envs/env-django-cloner/django-object-cloner/object_cloner_demo/app/utils.py", line 76, in clone_childs
    setattr(new_child, remote_field_name, object)
  File "/home/agus/envs/env-django-cloner/lib/python3.7/site-packages/django/db/models/fields/related_descriptors.py", line 546, in __set__
    % self._get_set_deprecation_msg_params(),
TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use comments.set() instead.
>>> 

错误来自setattr(...), and “改用comments.set()”,但我仍然困惑如何更新该 m2m 值?

new_child = self.clone_object(old_child)

if new_child is not None:
    setattr(new_child, remote_field_name, object)
    new_child.save()

我也尝试过下面的这段代码,但仍然有一个错误。克隆的 m2m 对象很多且未填充到 m2m 值中。

if new_child is not None:
    # check the object_type
    object_type = getattr(new_child, remote_field_name)

    if hasattr(object_type, 'pk'):
        # this mean is `object_type` as real object.
        # so, we can directly use the `setattr(...)`
        # to update the old relation value with new relation value.
        setattr(new_child, remote_field_name, object)

    elif hasattr(object_type, '_queryset_class'):
        # this mean is `object_type` as m2m queryset (ManyRelatedManager).
        # django.db.models.fields.related_descriptors.\
        # create_forward_many_to_many_manager.<locals>.ManyRelatedManager

        # check the old m2m values, and assign into new object.
        # FIXME: IN THIS CASE STILL GOT AN ERROR
        old_m2m_values = getattr(old_child, remote_field_name).all()
        object_type.add(*old_m2m_values)

    new_child.save()

我尝试用一​​些工作代码来解决这个有趣的问题......这比我最初想象的更难!

我偏离了您原来的解决方案,因为我在遵循 ObjectCloner 逻辑时遇到了一些困难。

我能想到的最简单的解决方案如下;我没有使用类,而是选择使用单个辅助函数克隆对象(),它处理单个对象。

当然,您可以使用第二个函数来处理对象列表或查询集,方法是扫描序列并多次调用clone_object()。

def clone_object(obj, attrs={}):

    # we start by building a "flat" clone
    clone = obj._meta.model.objects.get(pk=obj.pk)
    clone.pk = None

    # if caller specified some attributes to be overridden, 
    # use them
    for key, value in attrs.items():
        setattr(clone, key, value)

    # save the partial clone to have a valid ID assigned
    clone.save()

    # Scan field to further investigate relations
    fields = clone._meta.get_fields()
    for field in fields:

        # Manage M2M fields by replicating all related records 
        # found on parent "obj" into "clone"
        if not field.auto_created and field.many_to_many:
            for row in getattr(obj, field.name).all():
                getattr(clone, field.name).add(row)

        # Manage 1-N and 1-1 relations by cloning child objects
        if field.auto_created and field.is_relation:
            if field.many_to_many:
                # do nothing
                pass
            else:
                # provide "clone" object to replace "obj" 
                # on remote field
                attrs = {
                    field.remote_field.name: clone
                }
                children = field.related_model.objects.filter(**{field.remote_field.name: obj})
                for child in children:
                    clone_object(child, attrs)

    return clone

使用 Python 3.7.6 和 Django 3.0.6 进行测试的 POC 示例项目已保存在 github 上的公共存储库中:

https://github.com/morlandi/test-django-clone https://github.com/morlandi/test-django-clone

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Django 克隆递归对象 的相关文章

  • 如何在python中确定过去的时区特定日期是否是夏令时?

    有没有办法检查特定时区在我指定的日期是否处于夏令时 test dt datetime year 2015 month 2 day 1 pst pytz timezone America Los Angeles test dt pst loc
  • Seaborn regplot 中点和线的不同颜色

    中列出的所有示例西伯恩的regplot文档 https seaborn pydata org generated seaborn regplot html点和回归线显示相同的颜色 改变color争论改变了两者 如何为点设置与线不同的颜色 你
  • 如何使用 python http.server 运行 CGI“hello world”

    我使用的是 Windows 7 和 Python 3 4 3 我想在浏览器中运行这个简单的 helloworld py 文件 print Content Type text html print print print print h2 H
  • Python 在 64 位 vista 上获取 os.environ["ProgramFiles"] 的错误值

    Vista64 计算机上的 Python 2 4 3 环境中有以下2个变量 ProgramFiles C Program Files ProgramFiles x86 C Program Files x86 但是当我运行以下命令时 impo
  • Python 可选参数对

    我正在使用argparse模块获取两个可选的命令行参数 parser add argument start date nargs metavar START DATE help start date in YYYY MM DD parser
  • Python:绘制甘特图的模块

    有没有一个好的Python绘图模块甘特图 http en wikipedia org wiki Gantt chart 我试过了开罗情节 http linil wordpress com 2008 09 16 cairoplot 11 但它
  • Python 中使用 globals() 的原因?

    Python 中有 globals 函数的原因是什么 它只返回全局变量的字典 这些变量已经是全局的 所以它们可以在任何地方使用 我只是出于好奇而问 试图学习Python def F global x x 1 def G print glob
  • TypeError:“NoneType”对象不可下标[重复]

    这个问题在这里已经有答案了 错误 names curfetchone 0 TypeError NoneType object is not subscriptable 我尝试检查缩进 但仍然有错误 我读到 如果数据库中没有文件名记录 变量名
  • 如何在 django 中正确设置自定义 handler404?

    根据文档 https docs djangoproject com en dev topics http views customizing error views这应该相当简单 我只需要定义handler404 目前我正在做 在我的顶部u
  • Django - 使 ModelForm(ImageField 的)仅接受某些类型的图像

    我将 Pillow 2 3 0 与 Django 一起使用 并且在 models py 中有一个 ImageField 如下所示 class UserImages models Model user models ForeignKey Us
  • 如何在自定义 django 命令中抽象出命令代码

    我正在我的应用程序下编写自定义 django 命令management commands目录 目前我在该目录中有 6 个不同的文件 每个文件都有不同的命令来解决独特的需求 然而 有一些实用程序是它们所共有的 抽象出这些公共代码的最佳方法是什
  • Pandas 中的数据透视表小计

    我有以下数据 Employee Account Currency Amount Location Test 2 Basic USD 3000 Airport Test 2 Net USD 2000 Airport Test 1 Basic
  • 向结构化 numpy 数组添加字段

    将字段添加到结构化 numpy 数组的最简洁方法是什么 是否可以破坏性地完成 或者是否有必要创建一个新数组并复制现有字段 每个字段的内容是否连续存储在内存中 以便可以有效地完成此类复制 如果您使用 numpy 1 3 还有 numpy li
  • 列表中的“u”是什么意思?

    这是我第一次遇到这种情况 刚刚打印了一个列表 每个元素似乎都有一个u在它前面 即 u hello u hi u hey 它是什么意思 为什么列表的每个元素前面都会有这个 由于我不知道这种情况有多常见 如果您想了解我是如何遇到它的 我会很乐意
  • Django INSTALLED_APPS 的命名约定是如何工作的?

    该网站上的教程创建了一个名为 polls 的应用程序 它使用 django 1 9 所以在 INSTALLED APPS 中它是 polls apps PollsConfig 我正在观看一个教程 他将应用程序命名为新闻通讯 并且在 INST
  • python:xml.etree.ElementTree,删除“命名空间”

    我喜欢 ElementTree 解析 xml 的方式 特别是 Xpath 功能 我有一个带有嵌套标签的应用程序的 xml 输出 我想按名称访问此标签而不指定名称空间 这可能吗 例如 root findall molpro job 代替 ro
  • Matplotlib:检查空图

    我有一个循环加载并绘制一些数据 如下所示 import os import numpy as np import matplotlib pyplot as plt for filename in filenames plt figure i
  • Django:登录用户并在同一页面上刷新而不定义模板?

    我正在尝试使用引导下拉登录表单来让用户登录 我可以对我的用户名和密码进行硬编码并进行身份验证 但我试图在不进入登录屏幕的情况下让用户登录 这是我的代码 模板 我使用操作来调用 logUserIn url 以便表单可以发布到该视图 ul cl
  • 具有重复值的 Sqlite 列

    就说专栏吧aSQLite 数据库的非常重复 始终有相同的 4 个值 其他值可能稍后出现 但不同值的数量将少于 1000 个 VALUES hello world it s a shame to store this str many tim
  • 如何从Python枚举类中获取所有值?

    我正在使用 Enum4 库创建一个枚举类 如下所示 class Color Enum RED 1 BLUE 2 我要打印 1 2 作为某处的列表 我怎样才能实现这个目标 您可以执行以下操作 e value for e in Color

随机推荐

  • 如何利用方向角和速度计算下次的经纬度

    我知道我当前的位置 lat x lon y 我知道我的速度和方向角 如何预测下一次的下一个位置 首先 根据您当前的速度和已知的时间间隔 下一次 计算您将行驶的距离 distance speed time 然后您可以使用以下公式计算您的新位置
  • ListView 和 setAdapter 的 NullPointerException

    我不断收到以下 nullPointerException 并且我无法完全弄清楚任何帮助将不胜感激 程序从数据库中查询 然后使用适配器填充ListView public class ViewListingsActivity extends A
  • 为什么 readline.read_history_file 给我“IOError:[Errno 2]没有这样的文件或目录”

    我的 Python 历史文件位于 pyhistory 并包含以下内容 from project stuff import quit from project stuff import my thing Thing objects get i
  • 使用 ref 获取 React 中的 iframe 元素

    我有一个包含 iframe 的组件 我想在 React 中访问其内容 我使用 ref 来处理 iframe 如何从 iframe 获取所有锚点标签 这是我的代码 const GridGenerator gt const loading se
  • “真”和“假”在预处理器条件中具有通常的含义吗?

    给定一个 C 11 编译器 error最终结果应该是正确的吗 no includes define SOMEMACRO true if SOMEMACRO error it was true else error it was false
  • d3.js 中的核密度平滑

    抱歉 我既不是统计专家 也不是 d3 js 专家 我试图叠加两个平滑的直方图以显示两个相关分布的重叠 我想知道 d3 js 中内核密度或其他平滑的可用性 大约一年前似乎有一些关于该主题的活动 但我无法在主线 d3 js 代码中找到该函数 关
  • 如何将 VS Code 调试器与 webpack-dev-server 结合使用(忽略断点)

    我只是想让 VS Code 的调试器与 webpack dev server 一起工作 而不忽略我的断点 现在 webpack dev server 从内存中提供捆绑文件 而如果我理解正确的话 VS Code 调试器会在磁盘上搜索它们 或者
  • 我无法让客户端脚本连接到本地主机服务器,TCP 连接永远不会发生

    我编写了一个简单的客户端程序 它使用以下命令创建套接字 CFSteamCreatePairWithSocketToHost function 并连接到在端口 8080 上的本地主机上运行的服务器 它很好地创建了套接字 但它从不连接到服务器
  • SQL Server XML 添加属性(如果不存在)

    我正在尝试添加一个属性 如果它不存在 它应该很简单 但我对 XML XPath XQuery 等还不熟悉 所以请原谅我的无知 我希望能够传递 XML 数据并修改它 ALTER FUNCTION dbo ConvertXmlData xmlD
  • Kendo UI 网格插入/更新(再次)创建重复记录

    我在这个主题中遇到了与丹尼尔相同的问题 但他的解决方案对我不起作用 http www kendoui c om forums ui grid kendo ui grid inserts updates create duplicate re
  • Gmail 中添加了一个间隙,位于 Outlook 的 html 签名内

    我创建了一个需要在 Outlook 中使用的 html 签名 根据建议 我使用了表格布局 给定所有图像 甚至 td tr 和表格本身的特定高度和宽度 0 填充和边距 甚至尝试在 css 和旧式方式中添加这些内容实际标签 在 Outlook
  • 如何使用 LibGit2Sharp 向 VSTS 进行身份验证?

    我正在尝试使用 LibGit2Sharp 克隆 VSTS Visual Studio Team Services 存储库 我正在设置一个CredentialsHandler and UsernamePasswordCredentials代表
  • Primefaces 保存/传递过滤后的数据表结果列表

    目前 我正在使用具有排序 过滤功能的数据表成功地显示数据库中的图像元数据 在我的数据表下方 我使用第三方图像封面成功地显示了我的图像 http www jacksasylum eu ContentFlow http www jacksasy
  • 引导日期选择器

    我试图让引导日期选择器突出显示在下拉日期选择器中选择的日期 它目前没有这样做 我错过了什么 div class input append date div
  • 根据控制器中的变量显示或隐藏元素 - Ionic

    据我所知 这可能更多的是 AngularJS 问题 而不是 Ionic 特定问题 我的一个视图中有一个按钮
  • 获取个人应用代码并显示

    我正在尝试获取应用程序代码并显示它 例如 如果按钮 X 启动一个新活动 则 textView 显示整个方法 我只到达了如何以 HTML 格式显示代码这个问题 https stackoverflow com questions 1529068
  • 跟踪电子中的窗口大小

    我今天才开始玩 Electron 我需要能够获取可用的窗口大小 并在窗口大小调整时更新它 看起来这并不像传统的 JS 应用程序那么简单 跟踪窗口大小的推荐方法是什么 目前 我有我的主进程和一个渲染器 不打算一次打开超过 1 个渲染器 窗口
  • 如何从命令行发送电子邮件?

    我想从命令行快速发送电子邮件 我意识到可能有多种不同的方法可以做到这一点 我正在寻找一种从 Linux 终端 可能是 bash shell 但任何东西都可以 执行此操作的简单方法 以及在 Windows 上执行此操作的替代方法 我希望能够直
  • 如何在 UML 序列图中表示监听器

    在序列图中 如何表示事件触发的侦听器 它不仅仅是常规方法调用 因此这样显示似乎不正确 我尝试制作包含 JMS 侦听器的系统的序列图 我可以通过系统对 JMS 队列的 send 调用来启动生命线 将调用 send 的系统显示为业务参与者 或者
  • Django 克隆递归对象

    以前 当我想递归地克隆对象时 我遇到了问题 我知道克隆对象的简单方法是这样的 obj Foo objects get pk