【问题标题】:Issue with ManyToMany Relationships not updating immediately after save保存后不立即更新多对多关系的问题
【发布时间】:2010-12-27 21:04:21
【问题描述】:

我遇到了未更新的多对多关系问题 当我保存模型时(通过管理员)并尝试在模型中使用新值 附加到post_save 信号或save_model 内的函数 关联的AdminModel。 我试图通过使用重新加载这些函数中的对象 使用 id 获取函数.. 但它仍然具有旧值。

这是交易问题吗?是否有信号抛出时 交易结束?

谢谢,

【问题讨论】:

  • 那么,你是在改变对象的pk吗?
  • 我有一个对象,并且与其他对象存在多对多关系,但我可以获得更新的关系
  • 在这里查看我的答案:stackoverflow.com/a/50570747/5077059

标签: django django-admin django-orm django-signals


【解决方案1】:

当您通过管理表单保存模型时,它不是原子事务。首先保存主对象(以确保它具有 PK),然后 M2M 被清除 并将新值设置为表单中出现的任何值。因此,如果您在主对象的 save() 中,则您处于 M2M 尚未更新的机会窗口中。事实上,如果您尝试对 M2M 做某事,则更改将被 clear() 清除。大约一年前我遇到了这个问题。

与预 ORM 重构时代相比,代码有所改变,但归结为 django.db.models.fields.ManyRelatedObjectsDescriptor 和 ReverseManyRelatedObjectsDescriptor 中的代码。查看他们的 __set__() 方法,您会看到 manager.clear(); manager.add(*value) clear() 完成清除该表中当前主对象的所有 M2M 引用。然后 add() 设置新值。

所以回答你的问题:是的,这是一个交易问题。

事务结束时是否有信号抛出?没有任何官方信息,但请继续阅读:

有一个related thread a few months ago,MonkeyPatching 是提出的一种方法。 Grégoire posted a MonkeyPatch 为此。我没有尝试过,但它看起来应该可以工作。

【讨论】:

  • 我也遇到了这个问题,我使用了在这里找到的解决方案stackoverflow.com/questions/6200233/…
  • 如果我错了,请纠正我,但@peterrowell 的这个答案在当前版本的 Django (1.10) 中仍然有效。 M2M 字段首先清除,然后填充表单的数据。
  • @nik_m :自从我上次看这个已经有一段时间了。根本问题在于,这本质上不是原子事务,因为主模型必须在创建/更新 m2m 之前创建/修改(因为 m2m 引用了家长)。可能有提到here 和here 的解决方法。祝你好运!
  • 是的! Django 的m2m_changed 信号就是这样做的方法。虽然,在这种情况下,您将无法在 django 管理员的 save_model 中执行此操作,您可以在其中访问 request obj 并希望向管理员添加消息。反正没有它也能活!
【解决方案2】:

当您尝试访问模型的 post_save 信号中的 ManyToMany 字段时,相关对象已经被移除,并且在信号结束之前不会再次添加。

要访问这些数据,您必须绑定到 ModelAdmin 中的 save_related 方法。不幸的是,对于需要自定义的非管理员请求,您还必须在 post_save 信号中包含代码。

见:https://docs.djangoproject.com/en/1.7/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_related

例子:

# admin.py
Class GroupAdmin(admin.ModelAdmin):
    ...
    def save_related(self, request, form, formsets, change):
        super(GroupAdmin, self).save_related(request, form, formsets, change)
        # do something with the manytomany data from the admin
        form.instance.users.add(some_user)

然后在您的信号中,您可以进行与保存时相同的更改:

# signals.py
@receiver(post_save, sender=Group)
def group_post_save(sender, instance, created, **kwargs):
    # do somethign with the manytomany data from non-admin
    instance.users.add(some_user)
    # note that instance.users.all() will be empty from the admin: []

【讨论】:

    【解决方案3】:

    我有一个通用的解决方案,它似乎比猴子修补核心甚至使用 celery 更干净(尽管我确信有人可以找到它失败的区域)。基本上,我在管理员中为具有 m2m 关系的表单添加了一个 clean() 方法,并将实例关系设置为 clean_data 版本。这使得实例的保存方法可以使用正确的数据,即使它还没有“在书上”。试一试,看看效果如何:

    def clean(self, *args, **kwargs):
        # ... actual cleaning here
        # then find the m2m fields and copy from cleaned_data to the instance
        for f in self.instance._meta.get_all_field_names():
            if f in self.cleaned_data:
                field = self.instance._meta.get_field_by_name(f)[0]
                if isinstance(field, ManyToManyField):
                    setattr(self.instance,f,self.cleaned_data[f])
    

    【讨论】:

      【解决方案4】:

      见http://gterzian.github.io/Django-Cookbook/signals/2013/09/07/manipulating-m2m-with-signals.html

      问题: 当您在 post 或 pre_save 信号接收器中操作模型的 m2m 时,您的更改会在 Django 对 m2m 的后续“清除”中消失。

      解决方案: 在您发布或 pre_save 信号处理程序中,将另一个处理程序注册到您要更新其 m2m 的模型的 m2m 中间模型上的 m2m_changed 信号。

      请注意,第二个处理程序将接收多个 m2m_changed 信号,测试与它们一起传递的“action”参数的值是关键。

      在第二个处理程序中,检查“post_clear”操作。当您收到带有 post_clear 操作的信号时,m2m 已被 Django 清除,您有机会成功操作它。

      一个例子:

      def save_handler(sender, instance, *args, **kwargs):
          m2m_changed.connect(m2m_handler, sender=sender.m2mfield.through, weak=False)
      
      
      def m2m_handler(sender, instance, action, *args, **kwargs):
          if action =='post_clear':
              succesfully_manipulate_m2m(instance)
      
      
      pre_save.connect(save_handler, sender=YouModel, weak=False)
      

      见https://docs.djangoproject.com/en/1.5/ref/signals/#m2m-changed

      【讨论】:

      • 链接失效了。
      【解决方案5】:

      您可以在此线程中找到更多信息:Django manytomany signals?

      【讨论】:

      • 我在django 1.4中试过,没用,使用m2m_changed信号,分配m2m值会导致reursing错误,如果你断开连接然后分配,然后instance.save(),更新不太好.
      【解决方案6】:

      更新 m2m 的解决方案之一,以及更新您的模型之一。

      Django 1.11 and higher

      首先,通过管理面板的所有请求都是原子的。你可以看看ModelAdmin:

      @csrf_protect_m
      def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
          with transaction.atomic(using=router.db_for_write(self.model)):
              return self._changeform_view(request, object_id, form_url, extra_context)
      
      @csrf_protect_m
      def delete_view(self, request, object_id, extra_context=None):
          with transaction.atomic(using=router.db_for_write(self.model)):
              return self._delete_view(request, object_id, extra_context)
      

      您在更新期间可以观察到的行为,当您对 m2m 记录所做的更改未保存时,即使您在模型或信号中的保存方法中进行了更改,仅因为 m2m 表单重写所有记录而发生主对象更新后。

      这就是为什么,一步一步:

      1. 主对象已更新。

      2. 您的代码(在保存方法或信号中)进行了更改(您可以查看它们,只需在 ModelAdmin 中放置一个断点):

       def save_related(self, request, form, formsets, change):
           breakpoint()
           form.save_m2m()
           for formset in formsets:
               self.save_formset(request, form, formset, change=change)
      
      1. form.save_m2m() 获取放置在页面上的所有 m2m 值(粗略地说)并通过相关管理器替换所有 m2m 记录。这就是您在交易结束时看不到更改的原因。

      有一个解决方案:通过 m2m 进行更改 transaction.on_commit。 transaction.on_commit 将进行您的更改 提交事务时在 form.save_m2m() 之后。

      不幸的是,此解决方案的缺点 - 您对 m2m 的更改将在单独的事务中执行。

      【讨论】:

        猜你喜欢
        • 2014-09-02
        • 2018-07-22
        • 2011-06-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-17
        • 1970-01-01
        • 2011-01-21
        相关资源
        最近更新 更多