user2901792
user2901792

Reputation: 687

Automatically update a field on one model after saving another [Django 1.11][Python3.x]

I am learning how to use the post_save signal on Django. Though I am not getting any errors, what I want to do will not work.

I have a model (Model) that is supposed to aggregate and average the ratings from the Review model and saves that number in a field called "average_rating."

I want the averaging to be done once a user provides a rating on the "Review" model and for the average rating to automatically be calculated and updated on the "Model" model.

I know that I can do this with a post_save signal. But I am not sure how.

How can I do what I am trying to do?

Thanks in advance.

models.py

from django.db import models
from django.db.models import Func, Avg
from django.urls import reverse
from django.template.defaultfilters import slugify
from django.db.models.signals import post_save
from django.dispatch import receiver

class Model(models.Model):
    name = models.CharField(max_length=55, default='')
    slug = models.SlugField(unique=True)
    average_rating = models.DecimalField(decimal_places=1,
                                            max_digits=2,
                                            default=0.0,
                                            blank=True,
                                            null=True)

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.average_rating = self.review_set.aggregate(
                    rounded_avg_rating=Round(Avg('rating')))['rounded_avg_rating']
        self.slug = (slugify(self.name))
        super(Model, self).save(*args, **kwargs)


class Review(models.Model):
    RATING_CHOICES = [
        (1, '1'),
        (2, '2'),
        (3, '3'),
        (4, '4'),
        (5, '5'),
    ]
    model = models.ForeignKey(Model,
                                null=True)
    rating = models.IntegerField(choices=RATING_CHOICES,
                                    default=1)
    review_date = models.DateField(auto_now_add=True)
    reviewer = models.ForeignKey(User,
                                    null=True,
                                    related_name = 'review_user')
    comment = models.TextField(default='')

    class Meta:
        verbose_name_plural = 'Reviews'
        verbose_name = 'Review'

    def __str__(self):
        return self.model.name

@receiver(post_save, sender=Review)
def update_average_rating(sender, instance, **kwargs):
    instance.average_rating = Review.objects.aggregate(
                rounded_avg_rating=Round(Avg('rating')))['rounded_avg_rating']

Update:

Using the solution provided by RajKris, I got this:

class Review(models.Model):
    ....

    def save(self, *args, **kwargs):
        Model.save(avg_rating_flag=True)
        super(Review, self).save(*args, **kwargs)

Now I am getting this error: TypeError: save() missing 1 required positional argument: 'self'

RajKris's updated answer fixed my problem. Works like a charm.

def save(self, *args, **kwargs):   
    super(Review, self).save(*args, **kwargs)
    model_obj = self.model
    model_obj.average_rating = model_ob.review_set.aggregate(
                rounded_avg_rating=Round(Avg('rating')))['rounded_avg_rating']
    model_obj.save()

Upvotes: 1

Views: 2196

Answers (1)

rajkris
rajkris

Reputation: 1793

In your Review save function:

def save(self, *args, **kwargs):   
    super(Review, self).save(*args, **kwargs)
    model_obj = self.model
    model_obj.average_rating = model_obj.review_set.aggregate(
                rounded_avg_rating=Round(Avg('rating')))['rounded_avg_rating']
    model_obj.save()

What happens here is that, each time a review object is created the corresponding Model object get updated with the new average rating.

Upvotes: 2

Related Questions