如何在 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_remove
和pre/post_clear
,因此如果您想在删除后打印您的列表,则需要添加更多逻辑来检查action == "post_remove"
是否存在。
最后,以防万一您不知道,set([1, 2])
将替换您提供的两个浇头(pk=1
和 pk=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
GenericRelation 的 Django m2m_changed 信号,有可能吗?
删除关系时,Django Python m2m_change 不起作用