How to autoupdate category by the amount of products in django model

I have a model of the category with title and count and I also have another model called Products which has category as one of its foreign-key. How can I auto-update the category count field by the number of products that comes under the same category?

class Category(models.Model):
    title = models.CharField(max_length = 20)
    count = models.IntegerFIeld()

class Product(models.Model):
    title = models.CharField(max_length = 20)
    category = models.ForeignKey(Category, on_delete = models.CASCADE)

You can either override save method or write a signal to auto-update the field, i.e.:

class Category(models.Model):
    def save(self, *args, **kwargs):
        self.count = self.product_set.count()
        super().save(*args, **kwargs)

or with signals:

from django.dispatch import receiver
@receiver(post_save, sender=Category)
def category_post_save(sender, instance, *args, **kwargs):
    instance.count = instance.product_set.count()
    instance.save()

But I would suggest you using a calculated property:

class Category(models.Model):
    ...

    @property
    def count(self):
        return self.product_set.count()

If you want to keep the field count I recommend you use signals. You should use post_signal and pre_signal (to get the previous category from a query) on the model Product.

Here are the signals:

@receiver(signals.pre_save, sender=Product)
def product_pre_save_signal(sender, instance, *args, **kwargs):
    if instance.id:
        previous_instance = Product.objects.get(id=instance.id)
        if previous_instance.category != instance.category:
            instance.previous_category = previous_instance.category

@receiver(signals.post_save, sender=Product)
def product_post_save_signal(sender, instance, created, *args, **kwargs):
    if not created:
        if hasattr(instance, "previous_category"):
            if instance.previous_category:
                instance.previous_category.count -= 1
                instance.previous_category.save()
            if instance.category:
                instance.category.count += 1
                instance.category.save()
    else:
        if instance.category:
            instance.category.count += 1
            instance.category.save()

Another recommendation for this situation is, make it a read-only field(add editable=False to the field declaration), so no one can edit it from Django admin or serializer.

But I highly recommend you remove the field because it's hard to keep this field sync with the table Product, so calculate the value each time.

You can have almost any query that is related field count over Category or Product, so don't worry:)

Back to Top