当我保存模型(通过管理员)并尝试在附加到post_save
信号的函数中或save_model
关联的AdminModel
. 我试图通过使用带有 id 的 get 函数在这些函数中重新加载对象。但它仍然具有旧值。
这是交易问题吗?事务结束时是否有信号抛出?
谢谢,
当我保存模型(通过管理员)并尝试在附加到post_save
信号的函数中或save_model
关联的AdminModel
. 我试图通过使用带有 id 的 get 函数在这些函数中重新加载对象。但它仍然具有旧值。
这是交易问题吗?事务结束时是否有信号抛出?
谢谢,
当您通过管理表单保存模型时,它不是原子事务。首先保存主对象(以确保它具有 PK),然后清除M2M并将新值设置为表单中出现的任何值。因此,如果您在主对象的 save() 中,则您处于 M2M 尚未更新的机会窗口中。事实上,如果你尝试对 M2M 做点什么,更改将被 clear() 清除。大约一年前我遇到了这个问题。
与预 ORM 重构时代相比,代码有所改变,但归结django.db.models.fields.ManyRelatedObjectsDescriptor
为ReverseManyRelatedObjectsDescriptor
. 查看他们的 __set__() 方法,您会看到manager.clear(); manager.add(*value)
clear() 完成清除了该表中当前主对象的所有 M2M 引用。然后 add() 设置新值。
所以回答你的问题:是的,这是一个交易问题。
事务结束时是否有信号抛出?没有什么官方的,但请继续阅读:
几个月前有一个相关的线程,MonkeyPatching 是提出的一种方法。Grégoire 为此发布了一个 MonkeyPatch。我还没有尝试过,但它看起来应该可以工作。
当您尝试访问模型的 post_save 信号中的 ManyToMany 字段时,相关对象已被删除,并且在信号完成之前不会再次添加。
要访问这些数据,您必须绑定到 ModelAdmin 中的 save_related 方法。不幸的是,对于需要自定义的非管理员请求,您还必须在 post_save 信号中包含代码。
例子:
# 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: []
我对此有一个通用的解决方案,它似乎比猴子修补核心甚至使用 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])
见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
您可以在此线程中找到更多信息:Django manytomany 信号?
更新 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 之后重写了所有记录对象已更新。
这就是为什么,一步一步:
主对象已更新。
您的代码(在保存方法或信号中)进行了更改(您可以查看它们,只需在 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)
有一个解决方案:通过 m2m 进行更改
transaction.on_commit
。事务提交时,transaction.on_commit 将在 form.save_m2m() 之后进行更改。
不幸的是,这个解决方案的缺点 - 您对 m2m 的更改将在单独的事务中执行。