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

How to make the m2m_changed signal atomic in Django?

我的项目中有以下模型:

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 信号被调用了两次,因为有两个变化正在发生。在提交所有更改后,我如何才能只调用一次?澄清一下,我想添加两种浇头,但我只想在所有更改结束时发出一次信号。感谢您的帮助。

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

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

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

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

希望对您有所帮助!