m2m_changed 信号与多对多通过 - 保存
m2m_changed signal with many-to-many through - save
我有这两个基本款,
class Product(models.Model):
title = models.CharField(max_length=40)
description = models.TextField(blank=True)
price = models.DecimalField(decimal_places=2, max_digits=7, default=0)
...
和
class Cart(models.Model):
user = models.ForeignKey(
User, null=True, blank=True, on_delete=models.CASCADE)
products = models.ManyToManyField(
Product, blank=True, through='CartItem')
total = models.DecimalField(default=0.00, max_digits=7, decimal_places=2)
def recalculate_and_save(self):
print("recalculate_and_save running")
total = 0
for ci in self.cartitem_set.all():
total += ci.product.price*ci.quantity
self.total = total
self.save()
,以及上面多对多关系的辅助模型,用于计算数量:
class CartItem(models.Model):
cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.SmallIntegerField(default=1)
我想要实现的是每次添加或删除项目时自动计算购物车的总数。所以,
@receiver(m2m_changed, sender=CartItem)
def m2m_changed_cart_receiver(sender, instance, action, *args, **kwargs):
print(f"m2m_changed received; action={action}, instance={instance}")
instance.recalculate_and_save()
然后我意识到,在日志中看到 pre_save 和 post_save 操作,我可能做错了什么 - 在从父保存调用(两次)的函数中调用保存,如每个文档。那么第一个问题是——为什么它不让我进入无限循环?第二个(可能更重要)——为什么我只看到接收函数在从购物车中删除商品时执行,而不是在添加它们时执行?删除是通过
完成的
cart.products.remove(product_obj)
,但通过
添加
cart_obj.cartitem_set.add(cart_item_obj, bulk=False)
,这可能与为什么删除会触发接收者,而添加不会。但这使问题更加复杂 - 将发件人设置为 CartItem,我希望在产品上执行的删除会错过接收者,而不是添加,这直接在 cartitems 上工作(尽管删除产品也会删除 CartItems,通过 on_delete=级联)。
修改ManyToManyField
(本例为products
)时会触发m2m_changed
信号。
来自docs
:
Sent when a ManyToManyField is changed on a model instance
所以对于你的问题:
why doesn't it send me into an infinite loop?
这是因为recalculate_and_save
没有对m2m字段做任何操作products
,所以不会再次触发m2m_changed
信号,避免死循环。
why am I only seeing the receiver function executing on removing items from the cart, but not on adding them?
由于信号只会在修改products
时出现,用cartitem_set
做事不会触发。
所以修改cartitem_set
后需要手动调用recalculate_and_save
:
cart_obj.cartitem_set.add(cart_item_obj, bulk=False)
cart_obj.recalculate_and_save()
我有这两个基本款,
class Product(models.Model):
title = models.CharField(max_length=40)
description = models.TextField(blank=True)
price = models.DecimalField(decimal_places=2, max_digits=7, default=0)
...
和
class Cart(models.Model):
user = models.ForeignKey(
User, null=True, blank=True, on_delete=models.CASCADE)
products = models.ManyToManyField(
Product, blank=True, through='CartItem')
total = models.DecimalField(default=0.00, max_digits=7, decimal_places=2)
def recalculate_and_save(self):
print("recalculate_and_save running")
total = 0
for ci in self.cartitem_set.all():
total += ci.product.price*ci.quantity
self.total = total
self.save()
,以及上面多对多关系的辅助模型,用于计算数量:
class CartItem(models.Model):
cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.SmallIntegerField(default=1)
我想要实现的是每次添加或删除项目时自动计算购物车的总数。所以,
@receiver(m2m_changed, sender=CartItem)
def m2m_changed_cart_receiver(sender, instance, action, *args, **kwargs):
print(f"m2m_changed received; action={action}, instance={instance}")
instance.recalculate_and_save()
然后我意识到,在日志中看到 pre_save 和 post_save 操作,我可能做错了什么 - 在从父保存调用(两次)的函数中调用保存,如每个文档。那么第一个问题是——为什么它不让我进入无限循环?第二个(可能更重要)——为什么我只看到接收函数在从购物车中删除商品时执行,而不是在添加它们时执行?删除是通过
完成的cart.products.remove(product_obj)
,但通过
添加cart_obj.cartitem_set.add(cart_item_obj, bulk=False)
,这可能与为什么删除会触发接收者,而添加不会。但这使问题更加复杂 - 将发件人设置为 CartItem,我希望在产品上执行的删除会错过接收者,而不是添加,这直接在 cartitems 上工作(尽管删除产品也会删除 CartItems,通过 on_delete=级联)。
修改ManyToManyField
(本例为products
)时会触发m2m_changed
信号。
来自docs
:
Sent when a ManyToManyField is changed on a model instance
所以对于你的问题:
why doesn't it send me into an infinite loop?
这是因为recalculate_and_save
没有对m2m字段做任何操作products
,所以不会再次触发m2m_changed
信号,避免死循环。
why am I only seeing the receiver function executing on removing items from the cart, but not on adding them?
由于信号只会在修改products
时出现,用cartitem_set
做事不会触发。
所以修改cartitem_set
后需要手动调用recalculate_and_save
:
cart_obj.cartitem_set.add(cart_item_obj, bulk=False)
cart_obj.recalculate_and_save()