Django - 如何通过post_save信号保存m2m数据? [英] Django - How to save m2m data via post_save signal?

查看:130
本文介绍了Django - 如何通过post_save信号保存m2m数据?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(Django 1.1)我有一个Project模型,使用m2m字段跟踪其成员。它看起来像这样:

(Django 1.1) I have a Project model that keeps track of its members using a m2m field. It looks like this:

class Project(models.Model):
    members = models.ManyToManyField(User)
    sales_rep = models.ForeignKey(User)
    sales_mgr = models.ForeignKey(User)
    project_mgr = models.ForeignKey(User)
    ... (more FK user fields) ...

创建项目时,选择的 sales_rep sales_mgr project_mgr 等等用户添加到成员以使其更容易跟踪项目权限。这个方法到目前为止已经很好了。

When the project is created, the selected sales_rep, sales_mgr, project_mgr, etc Users are added to members to make it easier to keep track of project permissions. This approach has worked very well so far.

我现在处理的问题是如何在其中一个 User FK字段通过管理员更新。我已经尝试过这个问题的各种解决方案,但最简单的方法似乎是一个 post_save 信号,如下所示:

The issue I am dealing with now is how to update the project's membership when one of the User FK fields is updated via the admin. I've tried various solutions to this problem, but the cleanest approach seemed to be a post_save signal like the following:

def update_members(instance, created, **kwargs):
    """
    Signal to update project members
    """
    if not created: #Created projects are handled differently
        instance.members.clear()

        members_list = []
        if instance.sales_rep:
            members_list.append(instance.sales_rep)
        if instance.sales_mgr:
            members_list.append(instance.sales_mgr)
        if instance.project_mgr:
            members_list.append(instance.project_mgr)

        for m in members_list:
            instance.members.add(m)
signals.post_save.connect(update_members, sender=Project)  

但是,即使我通过管理员更改其中一个字段, Project 仍然拥有相同的成员我已经在其他项目中使用我自己的观点更新了m2m字段的成员,但是我从来没有必须让管理员发挥好的效果。

However, the Project still has the same members even if I change one of the fields via the admin! I have had success updating members m2m fields using my own views in other projects, but I never had to make it play nice with the admin as well.

我还应该采取另外一种方法,除了post_save信号以更新会员资格?感谢您的帮助!

Is there another approach I should take other than a post_save signal to update membership? Thanks in advance for your help!

更新:

只是为了澄清,当我将自己的表单保存在前端时,post_save信号正常工作(旧成员被删除,新添加的表单)。但是,当我通过管理员保存项目(成员保持不变)时,post_save信号无法正常工作。

Just to clarify, the post_save signal works correctly when I save my own form in the front end (old members are removed, and new ones added). However, the post_save signal does NOT work correctly when I save the project via the admin (members stay the same).

我认为Peter Rowell在这种情况下的诊断是正确的。如果我从管理表单中删除成员字段,则post_save信号正常工作。当包含该字段时,它会根据保存时表单中存在的值保存旧成员。无论在保存项目(无论是信号还是自定义保存方法)时,对成员m2m字段进行了什么更改,它将始终被保存之前的表单中存在的成员覆盖。感谢您的指点!

I think Peter Rowell's diagnosis is correct in this situation. If I remove the "members" field from the admin form the post_save signal works correctly. When the field is included, it saves the old members based on the values present in the form at the time of the save. No matter what changes I make to the members m2m field when project is saved (whether it be a signal or custom save method), it will always be overwritten by the members that were present in the form prior to the save. Thanks for pointing that out!

推荐答案

有同样的问题,我的解决方案是使用m2m_changed信号。您可以在两个地方使用它,如下例所示。

Having had the same problem, my solution is to use the m2m_changed signal. You can use it in two places, as in the following example.

保存的管理员将继续:


  • emit pre_clear

  • 清除关系

  • emit post_clear

  • emit pre_add

  • 再次填充

  • emit post_add

  • save the model fields
  • emit the post_save signal
  • for each m2m:
    • emit pre_clear
    • clear the relation
    • emit post_clear
    • emit pre_add
    • populate again
    • emit post_add

    这里有一个简单的例子,可以在实际保存数据之前更改保存的数据的内容。

    Here you have a simple example that changes the content of the saved data before actually saving it.

    class MyModel(models.Model):
    
        m2mfield = ManyToManyField(OtherModel)
    
        @staticmethod
        def met(sender, instance, action, reverse, model, pk_set, **kwargs):
            if action == 'pre_add':
                # here you can modify things, for instance
                pk_set.intersection_update([1,2,3]) 
                # only save relations to objects 1, 2 and 3, ignoring the others
            elif action == 'post_add':
                print pk_set
                # should contain at most 1, 2 and 3
    
    m2m_changed.connect(receiver=MyModel.met, sender=MyModel.m2mfield.through)
    

    您还可以收听 pre_remove post_remove pre_clear post_clear 。在我的情况下,我使用它们在另一个(启用的东西)的内容中过滤一个列表(活动的东西),而不依赖于保存列表的顺序:

    You can also listen to pre_remove, post_remove, pre_clear and post_clear. In my case I am using them to filter one list ('active things') within the contents of another ('enabled things') independent of the order in which lists are saved:

    def clean_services(sender, instance, action, reverse, model, pk_set, **kwargs):
        """ Ensures that the active services are a subset of the enabled ones.
        """
        if action == 'pre_add' and sender == Account.active_services.through:
            # remove from the selection the disabled ones
            pk_set.intersection_update(instance.enabled_services.values_list('id', flat=True))
        elif action == 'pre_clear' and sender == Account.enabled_services.through:
            # clear everything
            instance._cache_active_services = list(instance.active_services.values_list('id', flat=True))
            instance.active_services.clear()
        elif action == 'post_add' and sender == Account.enabled_services.through:
            _cache_active_services = getattr(instance, '_cache_active_services', None)
            if _cache_active_services:
                instance.active_services.add(*list(instance.enabled_services.filter(id__in=_cache_active_services)))
                delattr(instance, '_cache_active_services')
        elif action == 'pre_remove' and sender == Account.enabled_services.through:
            # de-default any service we are disabling
            instance.active_services.remove(*list(instance.active_services.filter(id__in=pk_set)))
    

    如果启用被更新(清除/删除+添加回,像管理员),然后活动缓存并在第一遍('pre_clear')中清除,然后在第二遍('post_add')后从缓存中添加。

    If the "enabled" ones are updated (cleared/removed + added back, like in admin) then the "active" ones are cached and cleared in the first pass ('pre_clear') and then added back from the cache after the second pass ('post_add').

    诀窍是更新另一个的m2m_changed信号的一个列表。

    The trick was to update one list on the m2m_changed signals of the other.

    这篇关于Django - 如何通过post_save信号保存m2m数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆