Как обновить поле модели, используя сигналы django при обновлении объекта в поле m2m? - PullRequest
0 голосов
/ 29 апреля 2020

Я пытаюсь обновить сумму заказа при обновлении количества в OrderItem. Для этого я использую django изменение m2m сигнала с действием post_add или post_remove. Вот моя модель:

class Item(models.Model):
    name = models.CharField(max_length=20, unique=True)
    price = models.DecimalField(max_digits=8, decimal_places=2)


class Order(models.Model):
    order_item = models.ManyToManyField('OrderItem')
    total = models.DecimalField(max_digits=8, decimal_places=2, default=0.0)


class OrderItem(models.Model):
    item = models.ForeignKey(Item, on_delete=models.PROTECT)
    quantity = models.IntegerField()
    total = models.DecimalField(max_digits=8, decimal_places=2, default=0.0)

Общий сигнал обновления OrderItem

def pre_save_order_tem_receiver(sender, instance, *args, **kwargs):
    """Receiver for updating total of OrderItem"""
    total = instance.item.price * instance.quantity
    instance.total = total


pre_save.connect(pre_save_order_tem_receiver, sender=OrderItem)

m2m изменяет сигнал

def m2m_changed_order_item_receiver(sender, instance, action, *args, **kwargs):
    """Receiver for updating total of Order through OrderItem"""
    if action in ["post_add", "post_remove"]:
        order_items = instance.order_item.all()
        total = 0
        for order_item in order_items:
            total += order_item.item.price * order_item.quantity
        instance.total = total
        instance.save()


m2m_changed.connect(m2m_changed_order_item_receiver, sender=Order.order_item.through)

Тестовый случай:

    def test_updating_order_item_quantity_in_order(self):
        order_item1, order_item1_data = create_sample_order_item(
            item=self.item1,
            quantity=2,
            data_only=False
        )

        order_item2, order_item2_data = create_sample_order_item(
            item=self.item2,
            quantity=2,
            data_only=False
        )

        order, _ = create_sample_order(
            order_items=[order_item1_data, order_item2_data],
            data_only=False
        )

        order_items = order.order_item.all()

        for order_item in order_items:
            if order_item == order_item2:
                order_item2.quantity = 10
                order_item2.save()
        order.save()

        # update
        order_item2_total = order_item2.item.price * 10

        # works completly fine but i'm searching for alternative method using signal
        # order.order_item.remove(order_item2)
        # order.order_item.add(order_item2)

        order.refresh_from_db()
        order_item1.refresh_from_db()
        order_item2.refresh_from_db()

        # check weather OrderItem total is updated or not
        self.assertEqual(order_item2.total, order_item2_total)

        # fails here
        self.assertEqual(order.total, order_item1.total + order_item2.total)

Он работает полностью нормально, когда я впервые удаляю объект OrderItem из Order, обновляю и добавляю его. т.е.

# remove order_item
order.order_item.remove(order_item2)

# perform update 
order_item.total = ......

# add back
order.order_item.add(order_item2)

Есть ли другой надежный метод, кроме удаления, обновления и добавления с использованием сигнала ??

ОБНОВЛЕНО:

Согласно подсказке предоставлено Мухаммадом Ихфажилла Я получил частично работающий код, который работает только из панели администратора.

def listen_order_item_change(sender, instance, **kwargs):
    orders = instance.order_set.all()
    for order in orders:
        # get all order items
        order_items = order.order_item.all()

        total = 0
        # find total for order
        for order_item in order_items:
            total += order_item.total
        order.total = total

        # save order
        order.save()
    # instance.save() -> causes RecursionError

post_save.connect(listen_order_item_change, sender=OrderItem)

Ответы [ 2 ]

1 голос
/ 30 апреля 2020

Я думаю, что вам нужно прослушивать сигнал post_save, чтобы сделать это, потому что вы обновляете отдельный объект.

Сигналы m2m_changed * post_add и post_remove отправляются только тогда, когда что-то есть добавлен в отношение модели или удален.

Вот фрагмент кода для использования post_save в экземпляре OrderItem.

def listen_order_item_change(sender, instance, **kwargs):
    # get all orders
    orders = instance.order_set.all()
    for order in orders:
        # update the each total for each order here

post_save.connect(listen_order_item_change, sender=OrderItem)
0 голосов
/ 30 апреля 2020

Я думаю, что этого можно добиться, просто вызвав сигнал post_save.

Прежде всего, вы можете добавить в модель OrderItem метод обновления суммы, который можно вызвать позже из post_save приемника сигнала.

Вот фрагмент для метода обновления, который должен быть определен внутри OrderItem модели.

  def update_total_price(self):
        """for updating the total_price of an ordered item"""
        quantity = Decimal(self.quantity)
        unit_price = self.item.price #for getting the unit price of the item
        new_total_price = quantity * unit_price
        self.total = new_total_price
        self.save()
        return new_total_price

И, наконец, фрагмент для использования сигнала post_save вместе с его приемником может быть

def post_save_orderitem_total_receiver(sender,instance, created, *args, **kwargs):
    if created:
        instance.update_total_price()

post_save.connect(post_save_orderitem_total_receiver, sender=OrderItem)
...