[Django]-How to calculate average of some field in Django models and send it to rest API?

3πŸ‘

βœ…

Well I am gonna explain this in details, average rating could be considered as a virtual field in Gigs, so it make sense to put it in there, so lets try that:

class Gigs(models.Model):
    ...

    @property
    def average_rating(self):
        return self.reviews.aggregate(Avg('rating'))['rating_avg']

so when you gonna retrieve a single Gigs, this is good and everything, but the problem is if you need the average in the list api, this is gonna make alot of extra queries(1 for each Gig). in that case it is better to do it in bulk and in the view, so:

class GigsListAll(ListModelMixin, GenericAPIView): # you should put the mixin before the main class :D
    serializer_class = GigsSerializer
    permission_classes = (AllowAny,)

    def get_queryset(self):

        return Gigs.objects.all().annotate(_average_rating=Avg('reviews__rating') # pay attention, it was annotated as _average_rating

and now we gonna change the virtual field in the model, and check if we have it precalculated, so:

class Gigs(models.Model):
...

    @property
    def average_rating(self):
        if hasattr(self, '_average_rating'):
            return self._average_rating
        return self.reviews.aggregate(Avg('rating'))

finally to use it in your serializer:

class GigsSerializer (serializers.ModelSerializer):
average_rating = serializers.SerializerMethodField()
def get_average_rating(self, obj):
    return obj.average_rating
class Meta:
    model = Gigs
    fields = ['id','title','category','price','details','seller','images','average_rating']

p.s. It is a best practice to set the related name for foreign keys, so change your reviews model like this:

class Reviews(models.Model):
    ...
    item = models.ForeignKey(Gigs , on_delete=models.CASCADE, related_name='reviews')
πŸ‘€Ehsan Nouri

1πŸ‘

First give your foreign key a name so you can reverse it:

class Reviews(models.Model):
    ...
    item =  models.ForeignKey(Gigs, on_delete=models.CASCADE, related_name='reviews')
    ...

Then you can do this in your serializer:

from rest_framework import serializers
from .models import Gigs, Reviews
from django.db.models import Avg

class GigsSerializer (serializers.ModelSerializer):
    class Meta:
        model = Gigs
        fields = ['id','title','category','price','details','seller','images','avg_rating']

    avg_rating = serializers.SerializerMethodField()

    def get_avg_rating(self, ob):
        # reverse lookup on Reviews using item field
        return ob.reviews.all().aggregate(Avg('rating'))['rating__avg']

class ReviewsSerializer (serializers.ModelSerializer):
    class Meta:
        model = Reviews
        fields = ['id','rating','comment','item','buyer','created_at']
πŸ‘€iri

0πŸ‘

It’s redundant but I post it also here.

Objects properties have to be serialized using SerializerMethodField.
To get values for these type of fields serializers look for methodsnamed get_ and raises errors if they are not defined. The get_ method must accept an object as a parameter that the serializer use to pass in the current serialized object, so you can access its properties.
In this case it have to be:

class GigsSerializer(serializers.MethodSerializer):
    average_rating = serializers.SerializerMethodField()

    def get_average_rating(self, obj):
        return obj.average_rating

    class Meta:
        model = Gigs
        fields = ["""your fields here"""]

Leave a comment