代码之家  ›  专栏  ›  技术社区  ›  diegueus9

保存后许多关系未立即更新的问题

  •  17
  • diegueus9  · 技术社区  · 16 年前

    我和很多没有更新的关系都有问题 附加到 post_save 信号或在 save_model 属于 AdminModel .

    这是交易问题吗?飞机起飞时是否发出信号 交易结束?

    谢谢

    6 回复  |  直到 8 年前
        1
  •  32
  •   Community Mohan Dere    9 年前

    通过管理表单保存模型时,它不是原子事务。首先保存主对象(以确保其具有PK),然后保存M2M 尝试

    与ORM重构之前的日子相比,代码有了一些变化,但归根结底,它是在 django.db.models.fields.ManyRelatedObjectsDescriptor ReverseManyRelatedObjectsDescriptor manager.clear(); manager.add(*value) 该clear()complete清除该表中当前主对象的所有M2M引用。然后,add()设置新值。

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

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

    related thread a few months ago Grégoire posted a MonkeyPatch

        2
  •  9
  •   Dan Swain    8 年前

    当您试图访问模型的post_保存信号中的多个字段时,相关对象已被删除,在信号完成之前不会再次添加。

    要访问此数据,您必须与ModelAdmin中与save_相关的方法相结合。不幸的是,对于需要定制的非管理员请求,您还必须在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: []
    
        3
  •  5
  •   nator    14 年前

    我有一个通用的解决方案,看起来比猴子修补核心或者甚至用芹菜更干净(尽管我相信有人会找到它失败的地方)。基本上,我在admin中为具有m2m关系的表单添加了clean()方法,并将实例关系设置为clean_数据版本。这使得正确的数据可用于实例的save方法,即使它还没有“在书上”。尝试一下,看看效果如何:

    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])
    
        4
  •  3
  •   gterzian    12 年前

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

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

    解决方案:

    在第二个处理程序中,检查“post_clear”操作。当您收到带有post_清除操作的信号时,Django已清除m2m,您有机会成功操纵它。

    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

        5
  •  0
  •   Community Mohan Dere    9 年前

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

        6
  •  0
  •   Greg Eremeev    7 年前

    更新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. 您的代码(在save方法或信号中)进行了更改(您可以查看它们,只需在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. save_m2m()接受页面上的所有m2m值(粗略地说),并通过相关管理器替换所有m2m记录。这就是为什么在事务结束时看不到更改的原因。

    有一个解决方案:通过m2m进行更改 transaction.on_commit . transaction.on\u commit将进行更改 提交事务时,在form.save_m2m()之后。

    不幸的是,此解决方案的缺点是,您对m2m的更改将在单独的事务中执行。