sonarforte
sonarforte

Reputation: 1624

Detecting changes in related models in Django Rest Framework

I have a model in django, let's call it product, that is referenced by multiple purchase instances.

A purchase can be made for any number of items of the product, subject to the following constraint:

The total of all items in all purchases for a given product must be less than a set maximum number of items, which is defined differently for each product.

Users may create a purchase for a product, and I want to keep track of the total number of items covered by all purchases at any given time.

This is complicated by the fact that users can modify or delete their purchase, thereby changing the total number of items purchased.

How would I go about keeping track of this number for each product and updating it every time a purchase is changed? Is there a hook that can listen to the purchases for a product and detect a change?

purchase model:

class Purchase(models.Model):
    items = models.IntegerField(blank=False, default=1)
    delivery_method = models.CharField(max_length=100, blank=False, default='')
    #...
    product = models.ForeignKey('product.Product',
                             related_name='purchases', on_delete=models.CASCADE)

product model:

class Product(models.Model):
    name = models.CharField(max_length=100, blank=False,)
    items_offered = models.IntegerField(blank=False, default=2)
    # Items purchased should be the total number 
    # of items in all purchases for this product... 
    # How do I keep it updated?
    items_purchased = models.IntegerField(blank=False, default=0)

Upvotes: 0

Views: 379

Answers (2)

user8060120
user8060120

Reputation:

Simple way is override save, delete methods, or use django signals:

class Purchase(models.Model):
    # ... model definition
    def update_items_purchased(self, product):
        purchases = Purchase.objects.filter(product=product)
        if purchases:
            total = purchases.aggregate(total=Sum('items')).get('total', 0)
        else:
            total = 0
        product.items_purchased = total
        product.save()

    def save(self, *args, **kwargs):
        super(Purchase, self).save(*args, **kwargs)
        self.update_items_purchased(self.product)


    def delete(self, *args, **kwargs):
        super(Purchase, self).delete(*args, **kwargs)
        self.update_items_purchased(self.product)

Upvotes: 1

Cory Madden
Cory Madden

Reputation: 5193

Use a post_save signal.

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=Purchase)
def update_purchase_amounts(sender, instance, created, **kwargs):
     product = Product.objects.get(id=instance.product.id)
     product.items_purchased += instance.items
     product.save(update_fields=['items_purchased'])

I'm assuming purchase.items is the number of product in the Purchase.

Though, you may want to do it differently like aggregate all Purchase.items fields for the product so that you don't keep updating the amount purchased every time you save a Purchase instance. So maybe use something like:

from django.db.models.aggregates import Sum
counts = Purchase.objects.filter(product=instance.id).aggregate(Sum('items'))
product.items_purchased = counts
product.save(update_fields=['items_purchased'])

Upvotes: 1

Related Questions