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