InvAdrian
InvAdrian

Reputation: 33

Django post_save not firing but post_delete in same signals.py file does

I am hoping someone can explain to me why post_save is not working when post_delete seemingly is and they're very similar and in the same signals.py file:

Django version 3+ Python version 3+

models.py:

import uuid
from django.db import models
from django.db.models import Sum
from django.conf import settings
from products.models import Product
from shows.models import ShowsTickets

class Order(models.Model):
    order_number = models.CharField(max_length=32, null=False, editable=False)
    full_name = models.CharField(max_length=50, null=False, blank=False)
    email = models.EmailField(max_length=254, null=False, blank=False)
    phone_number = models.CharField(max_length=20, null=False, blank=False)
    country = models.CharField(max_length=40, null=False, blank=False)
    postcode = models.CharField(max_length=20, null=True, blank=True)
    town_or_city = models.CharField(max_length=40, null=False, blank=False)
    street_address1 = models.CharField(max_length=80, null=False, blank=False)
    street_address2 = models.CharField(max_length=80, null=True, blank=True)
    county = models.CharField(max_length=40, null=True, blank=True)
    date = models.DateTimeField(auto_now_add=True)
    delivery_cost = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)
    products_total = models.DecimalField(max_digits=6, decimal_places=2, null=True, default=0)
    tickets_total = models.DecimalField(max_digits=6, decimal_places=2, null=True, default=0)
    order_total = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)
    grand_total = models.DecimalField(max_digits=6, decimal_places=2, null=False, default=0)

    def _generate_order_number(self):
        """Generate a random, unique order number using UUID"""

        return uuid.uuid4().hex.upper()

    def update_total(self):
        """Update grand total each time a line item is added,
        accounting for delivery costs.
        """

        self.products_total = (self.productlineitems.aggregate(
            Sum('productlineitems_total'))['productlineitems_total__sum']) or 0
        self.tickets_total = (self.ticketlineitems.aggregate(
            Sum('ticketlineitems_total'))['ticketlineitems_total__sum']) or 0
        self.order_total = self.products_total + self.tickets_total
        self.delivery_cost = settings.STANDARD_DELIVERY_CHARGE
        self.grand_total = self.order_total + self.delivery_cost
        self.save()

    def save(self, *args, **kwargs):
        """Override the original save method to set the order number
        if it hasn't been set already.
        """

        if not self.order_number:
            self.order_number = self._generate_order_number()
        super().save(*args, **kwargs)

    def __str__(self):
        return self.order_number


class OrderLineProductItem(models.Model):
    order = models.ForeignKey(Order, null=False, blank=False, on_delete=models.CASCADE, related_name='productlineitems')
    product = models.ForeignKey(Product, null=False, blank=False, on_delete=models.CASCADE)
    quantity = models.IntegerField(null=False, blank=False, default=0)
    productlineitems_total = models.DecimalField(max_digits=6, decimal_places=2, null=False, blank=False, editable=False)

    def save(self, *args, **kwargs):
        """Override the original save method to set the lineproductitem total
        and update the order total.
        """

        self.productlineitems_total = self.product.price * self.quantity
        super().save(*args, **kwargs)

    def __str__(self):
        return f'{self.product} on order {self.order.order_number}'


class OrderLineTicketItem(models.Model):
    order = models.ForeignKey(Order, null=False, blank=False, on_delete=models.CASCADE, related_name='ticketlineitems')
    ticket = models.ForeignKey(ShowsTickets, null=False, blank=False, on_delete=models.CASCADE)
    quantity = models.IntegerField(null=False, blank=False, default=0)
    ticketlineitems_total = models.DecimalField(max_digits=6, decimal_places=2, null=False, blank=False, editable=False)

    def save(self, *args, **kwargs):
        """Override the original save method to set the lineticketitem total
        and update the order total.
        """

        self.ticketlineitems_total = self.ticket.price * self.quantity
        super().save(*args, **kwargs)

    def __str__(self):
        return f'{self.ticket} on order {self.order.order_number}'

signals.py:

from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from .models import OrderLineProductItem, OrderLineTicketItem


# not being called on update/create
@receiver(post_save, sender=OrderLineTicketItem)
@receiver(post_save, sender=OrderLineProductItem)
def update_on_save(sender, instance, created, **kwargs):
    """Update order total on lineproductitem and
    lineticketitem update/create
    """
    
    print('Hello, signals here!')
    instance.order.update_total()


# works fine and, when called, updates everything accordingly
@receiver(post_delete, sender=OrderLineTicketItem)
@receiver(post_delete, sender=OrderLineProductItem)
def update_on_save(sender, instance, **kwargs):
    """Update order total on lineproductitem and
    lineticketitem delete"""

    print('Hello, signals here!')
    instance.order.update_total()

I have also added import signals to the app's config class' ready() method.

The idea is for the signals to update the Order's product_total, ticket_total, and order_total fields through the update_total() method.

When creating an instance, or updating one, with either item, the totals are not calculated. However, when an instance is deleted, everything works as expected and the post_delete signal fires (I tested this with a print() function as you can see above.)

Why is the post_save part not firing at all (upon create or update)?

Upvotes: 0

Views: 1495

Answers (1)

InvAdrian
InvAdrian

Reputation: 33

OK, so I have fixed the issue. It turned out to be a rookie oversight on my part.

My signal functions had the same name. Changing the @post_delete function to update_on_delete() - instead of update_on_save() - did the trick.

Upvotes: 1

Related Questions