Django - 如何通过post_save信号保存m2m数据?

Mic*_*all 12 membership django django-admin

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

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) ...
Run Code Online (Sandbox Code Playgroud)

当创建项目时,选择sales_rep,sales_mgr,project_mgr,等Users的加入成员,使其更容易跟踪项目的权限.到目前为止,这种方法运作良好.

我现在要处理的问题是当User通过管理员更新其中一个FK字段时如何更新项目的成员资格.我已经尝试过各种解决方案来解决这个问题,但最干净的方法似乎是post_save如下信号:

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

但是,Project即使我通过管理员更改其中一个字段,仍然具有相同的成员!我已经成功使用我自己的视图在其他项目中更新成员m2m字段,但我从来没有让它与管理员一起玩得很好.

除了post_save信号之外,还有其他方法可以更新会员资格吗?在此先感谢您的帮助!

更新:

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

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

rew*_*ten 7

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

保存后的管理员将继续:

  • 保存模型字段
  • 发出post_save信号
  • 每平方米:
    • 发出pre_clear
    • 明确这种关系
    • 发出post_clear
    • 发出pre_add
    • 再次填充
    • 发出post_add

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

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

你也可以听pre_remove,post_remove,pre_clearpost_clear.在我的情况下,我使用它们来过滤另一个("启用的东西")内容中的一个列表("活动的东西"),而不依赖于保存列表的顺序:

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

如果"已启用"的更新(清除/删除+添加,如管理员),则"活动"缓存并在第一次传递('pre_clear')中清除,然后在第二次传递后从缓存中添加回来( 'post_add').

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


Dan*_*man 4

我看不出您的代码有任何问题,但我很困惑为什么您认为管理员的工作方式应该与任何其他应用程序不同。

但是,我必须说我认为你的模型结构是错误的。我认为您需要摆脱所有这些外键字段,而只拥有一个 ManyToMany - 但使用直通表来跟踪角色。

class Project(models.Model):
    members = models.ManyToManyField(User, through='ProjectRole')

class ProjectRole(models.Model):
    ROLES = (
       ('SR', 'Sales Rep'),
       ('SM', 'Sales Manager'),
       ('PM', 'Project Manager'),
    )
    project = models.ForeignKey(Project)
    user = models.ForeignKey(User)
    role = models.CharField(max_length=2, choices=ROLES)
Run Code Online (Sandbox Code Playgroud)