【问题标题】:Django - How to save m2m data via post_save signal?Django - 如何通过 post_save 信号保存 m2m 数据?
【发布时间】:2011-05-24 20:39:31
【问题描述】:

(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) ...

创建项目时,会将选定的sales_repsales_mgrproject_mgrUsers 添加到成员中,以便更轻松地跟踪项目权限。到目前为止,这种方法效果很好。

我现在处理的问题是当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)  

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

除了 post_save 信号来更新会员资格之外,我还应该采取其他方法吗?提前感谢您的帮助!

更新:

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

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

【问题讨论】:

  • 我不知道这是否是您的问题,但我有一种直觉,您可能遇到了表单代码如何更新 m2m 信息的工件。基本上,他们首先保存主要对象,然后通过首先清除所有对象来设置 m2m 值,然后根据表单中存在的值设置它们。这发生在 主对象上的 save() 之后,因此您在 save() 中或基于 post_save 信号所做的任何事情都首先完成,然后 撤消 .这是django.forms.models.save_instance()。如果有after_form_save 信号就好了。
  • 谢谢,彼得!我相信你的诊断是正确的。我更新了我的原始帖子以包含此信息。
  • 彼得是对的。我遇到了同样的问题并找到了解决方法,但它不像“after_form_save”信号那样简洁:stackoverflow.com/questions/3652585/…
  • 把我的培根救了下来!在过去的 30 分钟里,我一直在努力解决这个问题。谢谢!

标签: django django-admin membership


【解决方案1】:

遇到同样的问题,我的解决方案是使用 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)

您还可以收听pre_removepost_removepre_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)))

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

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

【讨论】:

    【解决方案2】:

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

    但是,我必须说我认为您的模型结构是错误的。我认为您需要摆脱所有这些 ForeignKey 字段,并且只拥有一个 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)
    

    【讨论】:

    • 我同意应该改进模型结构,但我正在使用较旧的实现并试图充分利用它。目前,我还没有准备好将系统迁移到这个新结构,但我会记住您的建议以备将来使用。谢谢。
    【解决方案3】:

    当我需要从一组项目中找到通过 m2m_field 连接到模型的最新项目时,我陷入了困境。

    按照 Saverio 的回答,以下代码解决了我的问题:

    def update_item(sender, instance, action, **kwargs):
        if action == 'post_add':
            instance.related_field = instance.m2m_field.all().order_by('-datetime')[0]
            instance.save()
    
    m2m_changed.connect(update_item, sender=MyCoolModel.m2m_field.through)
    

    【讨论】:

      猜你喜欢
      • 2023-03-05
      • 2012-01-29
      • 1970-01-01
      • 2021-10-26
      • 2011-03-05
      • 1970-01-01
      • 2012-11-17
      • 2013-03-01
      • 2012-10-12
      相关资源
      最近更新 更多