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

dar*_*rse 1 django many-to-many django-models django-orm

我的项目中有以下模型:

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

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

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

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

pizza = Pizza.objects.get(pk=1)
pizza.toppings.set([1, 2])
Run Code Online (Sandbox Code Playgroud)

配料设置正确,但是,披萨的 save 方法被调用两次,因为 m2m_changed 信号被调用两次,因为发生了两次更改。在提交所有更改后,如何才能仅调用一次?为了澄清,我希望添加两种配料,但我只想在所有更改结束时触发一次信号。谢谢你的帮助。

ahm*_*iry 6

m2m_changed信号有点奇怪,因为它被调用了两次:一次使用 的操作pre_add,一次使用 的操作post_add(请参阅文档)。由于您正在调用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()
Run Code Online (Sandbox Code Playgroud)

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

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

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

希望这可以帮助!