如何在 Django 中使 m2m_changed 信号原子化?

Posted

技术标签:

【中文标题】如何在 Django 中使 m2m_changed 信号原子化?【英文标题】:How to make the m2m_changed signal atomic in Django? 【发布时间】:2019-10-17 20:34:13 【问题描述】:

我的项目中有以下模型:

class Topping(models.Model):
    name = models.CharField(max_length=255)

class Pizza(models.Model):
    name = models.CharField(max_length=255)
    toppings = models.ManyToManyField(Topping, blank=True)

    def save(self, *args, **kwargs):
        print('Saving...')
        if self.pk:
            for topping in self.toppings.all():
                print(topping.name)

        super(Pizza, self).save(*args, **kwargs)

def toppings_changed(sender, instance, **kwargs):
    instance.save()

m2m_changed.connect(toppings_changed, sender=Pizza.toppings.through)

基本上,每当toppings 更改时,都会触发信号。信号所做的只是调用Pizza 对象的保存方法。无论如何,假设我有三个对象:

pizza = Pizza.objects.get(pk=1) # Number of toppings is 0
topping1 = Topping.objects.get(pk=1)
topping2 = Topping.objects.get(pk=2)

现在,我想为我的披萨添加两种配料。我使用以下代码执行此操作:

pizza = Pizza.objects.get(pk=1)
pizza.toppings.set([1, 2])

配料设置正确,但是,披萨的保存方法被调用了两次,因为 m2m_changed 信号被调用了两次,因为发生了两次变化。在提交所有更改后,如何仅调用一次?澄清一下,我希望添加两个浇头,但我只想在所有更改结束时触发一次我的信号。感谢您的帮助。

【问题讨论】:

【参考方案1】:

m2m_changed 信号有点奇怪,因为它被调用了两次:一次是pre_add 的动作,一次是post_add 的动作(参见docs)。由于您为任何操作调用toppings_changed(),它最终会被调用两次,因此save() 将被调用两次。

您似乎对post_add 感兴趣,所以我会这样做:

@receiver(m2m_changed, sender=Pizza.toppings.through)
def toppings_changed(sender, instance, action, **kwargs):
    if action == "post_add":
        instance.save()

在这里,instance.save() 应该只被调用一次,不管你传递给set() 的参数有多少。请注意,如果您使用接收器装饰器,则无需执行m2m_changed.connect(...)

以上内容也适用于pre/post_removepre/post_clear,因此如果您想在删除后打印您的列表,则需要添加更多逻辑来检查action == "post_remove" 是否存在。

最后,以防万一您不知道,set([1, 2])替换您提供的两个浇头(pk=1pk=2)。如果您只是想在现有的套餐中添加浇头,我会使用add(1, 2)

希望这会有所帮助!

【讨论】:

非常好的答案。非常感谢。但是有一个问题,如果删除相关的Topping 对象,将逻辑设置为if action == 'post_add' or action == 'post_remove' or action == 'post_delete': 并不会真正调用save 方法。对于添加和删除,它可以完美运行。我做错了吗? 啊,我的错!我偶然输入了post_delete 而不是post_remove(现在编辑)。 m2m_changed 仅接收前/后 _add_remove_clear 操作。 post_delete 信号是独立的,您需要将另一个 @receiver 附加到您的函数中。在您的情况下,它看起来像:@receiver(post_delete, sender=Topping)。澄清; post_delete 在删除浇头对象时调用,post_remove 在从比萨饼中删除浇头时调用(不一定一起删除)。

以上是关于如何在 Django 中使 m2m_changed 信号原子化?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Django 的 m2m_changed 修改正在保存的内容 pre_add

Django:删除关系结束时未触发 m2m_changed

GenericRelation 的 Django m2m_changed 信号,有可能吗?

删除关系时,Django Python m2m_change 不起作用

如何在 django 中使 @cached_property 无效

如何在 Django Administration 中使文本加粗