我遇到了没有更新的ManytoMany关系的问题
在我保存它的模型中(通过管理员)并尝试在a中使用新值
附加到post_save
信号或save_model
内的函数
关联的AdminModel
。
我试图通过使用。重新加载这些函数中的对象
获取具有id的函数..但它仍然具有旧值。
这是交易问题吗?是什么时候会抛出一个信号 交易结束了吗?
谢谢,
答案 0 :(得分:25)
当您通过管理表单保存模型时,它不是原子事务。首先保存主对象(以确保它具有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为此。我没试过,但它看起来应该有用。
答案 1 :(得分:7)
当您尝试访问模型的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: []
答案 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格式重写了所有记录,所以即使未保存这些更改在更新主要对象之后。
这就是为什么要逐步进行:
主要对象已更新。
您的代码(在保存方法或信号中)进行了更改(您可以查看它们,只需在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进行的更改将在单独的事务中执行。