Сигнал m2m_changed со сквозным прохождением "многие-ко-многим" - сохранение
У меня есть две основные модели,
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, я понял, что, скорее всего, делаю что-то не так - вызываю save в функции, которая вызывается (дважды) из родительского save, согласно документации. Тогда первый вопрос - почему это не отправляет меня в бесконечный цикл? И второй (и, вероятно, более важный) - почему я вижу, что функция-приемник выполняется только при удалении товаров из корзины, но не при их добавлении? Удаление происходит через
cart.products.remove(product_obj)
, но добавление via
cart_obj.cartitem_set.add(cart_item_obj, bulk=False)
, что, вероятно, связано с тем, почему удаление срабатывает на получателя, а добавление - нет. Но это еще больше усложняет вопрос - имея отправителем CartItem, я бы ожидал, что удаление, выполняемое для продукта, пропустит получателя, а не добавление, которое работает непосредственно с cartitems (хотя удаление продуктов удаляет и CartItems, через on_delete=CASCADE).
При модификации m2m_changed
(в данном случае ManyToManyField
) срабатывает сигнал products
.
От docs
:
Sent when a ManyToManyField is changed on a model instance
Итак, на ваши вопросы:
почему он не отправляет меня в бесконечный цикл?
Это происходит потому, что recalculate_and_save
ничего не сделал с полем m2m products
, поэтому он не вызовет сигнал m2m_changed
снова, избегая бесконечного цикла.
почему я вижу выполнение функции приемника только при удалении товаров из корзины, но не при их добавлении?
Поскольку сигнал будет поступать только при модификации products
, действия с cartitem_set
не вызовут его.
Поэтому вам нужно вручную вызвать recalculate_and_save
после модификации cartitem_set
:
cart_obj.cartitem_set.add(cart_item_obj, bulk=False)
cart_obj.recalculate_and_save()