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

die*_*us9 22 django django-signals django-orm django-admin

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

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

谢谢,

Pet*_*ell 28

当您通过管理表单保存模型时,它不是原子事务.首先保存主对象(以确保它具有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.我没试过,但它看起来应该有用.


fre*_*reb 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)
Run Code Online (Sandbox Code Playgroud)

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

# 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: []
Run Code Online (Sandbox Code Playgroud)


小智 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])
Run Code Online (Sandbox Code Playgroud)