25

当我保存模型(通过管理员)并尝试在附加到post_save信号的函数中或save_model关联的AdminModel. 我试图通过使用带有 id 的 get 函数在这些函数中重新加载对象。但它仍然具有旧值。

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

谢谢,

4

6 回答 6

33

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

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

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

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

几个月前有一个相关的线程,MonkeyPatching 是提出的一种方法。Grégoire 为此发布了一个 MonkeyPatch。我还没有尝试过,但它看起来应该可以工作。

于 2009-12-18T01:41:13.753 回答
9

当您尝试访问模型的 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: []
于 2015-02-11T23:41:06.523 回答
5

我对此有一个通用的解决方案,它似乎比猴子修补核心甚至使用 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])
于 2011-08-09T18:12:42.003 回答
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 信号,测试与它们一起传递的“动作”参数的值是关键。

在第二个处理程序中,检查“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

于 2013-09-06T14:55:49.060 回答
0

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

于 2011-05-18T16:06:50.690 回答
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 表单在 main 之后重写了所有记录对象已更新。

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

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

于 2019-02-14T22:49:57.473 回答