ManyToMany关系的问题在保存后不会立即更新

时间:2009-12-17 23:44:54

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

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

这是交易问题吗?是什么时候会抛出一个信号 交易结束了吗?

谢谢,

6 个答案:

答案 0 :(得分:25)

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

代码在ORM前重构日期有所改变,但归结为django.db.models.fields.ManyRelatedObjectsDescriptorReverseManyRelatedObjectsDescriptor中的代码。查看它们的__set __()方法,您将看到manager.clear(); manager.add(*value) clear()完成清除该表中当前主对象的任何M2M引用。然后add()设置新值。

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

交易结束时是否会抛出信号?没有官方的,但请继续阅读:

有一个related thread a few months ago和MonkeyPatching是一种方法。 Grégoire posted a MonkeyPatch为此。我没试过,但它看起来应该有用。

答案 1 :(得分:7)

当您尝试访问模型的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: []

答案 2 :(得分:5)

我有一个通用的解决方案似乎比猴子修补核心甚至使用芹菜更清洁(虽然我确信有人可以找到它失败的区域)。基本上我在admin中为具有m2m关系的表单添加了一个clean()方法,并将实例关系设置为cleaning_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])

答案 3 :(得分:3)

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

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

溶液: 在post或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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

一种更新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进行的更改将在单独的事务中执行。