Home > front end >  Django Rest Framework - How do I get the count and average of a related field in a serializer
Django Rest Framework - How do I get the count and average of a related field in a serializer

Time:02-02

I have a post model that can have ratings:

Here is the post model:

class Post(models.Model):

    category = models.ForeignKey(Category, on_delete=models.SET_DEFAULT, default=1, related_name="posts")
    body = models.TextField("content", blank=True, null=True, max_length=5000)
    slug = AutoSlugField(populate_from=["category", "created_at"])
    video = models.FileField(upload_to=video_directory_path, null=True, blank=True)
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="posts"
    )
    published = models.BooleanField(default=False)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = "post"
        verbose_name_plural = "posts"
        db_table = "posts"
        ordering = ["created_at"]
        get_latest_by = "created_at"

    def __str__(self):
        return self.body[0:30]

    def get_absolute_url(self):
        return self.slug

    def one_image(self):
        return PostImage.objects.all().filter(post=self)[:1]

and here is the post rating model:

class PostRating(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="ratings")
    score = models.IntegerField(choices=list(zip(range(1, 6), range(1, 6))))
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, verbose_name="user rating"
    )
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="created at")
    updated_at = models.DateTimeField(auto_now=True, verbose_name="updated at")

    class Meta:
        verbose_name = "post rating"
        verbose_name_plural = "post ratings"
        db_table = "post_ratings"

    def __str__(self):
        return str(self.score)

Here is the serializer:

class PostSerializer(serializers.ModelSerializer):
    images = PostImageSerializer(many=True, read_only=True, required=False)
    
    class Meta:
        model = Post
        fields = [
            "id",
            "category",
            "body",
            "images",
            "video",
            "ratings",
            "published",
            "created_at",
            "updated_at",
        ]
        depth = 1


    def create(self, validated_data):
        user = User.objects.get(id=self.context['request'].data.get('user'))
        category = Category.objects.get(id=self.context['request'].data.get('category'))
        new_post = Post.objects.create(**validated_data, category=category, user=user)
        images = dict((self.context['request'].FILES).lists()).get('images', None)
        if images:
            for image in images:
                PostImage.objects.create(
                    image=image, post=new_post
                )
        return new_post

QUESTION:

In the serializer I have the related field 'ratings' and it displays an array with each of the ratings, which is great but I only want to get the total amount of ratings and the average. How can I get that in the serializer so I can display it on the front end as a ratings total and a ratings average.

CodePudding user response:

If I understand your question You can do this way.

class PostSerializer(serializers.ModelSerializer):
    images = PostImageSerializer(many=True, read_only=True, required=False)
    total_ratings = serializers.SerializerMethodField()
    average_rating = serializers.SerializerMethodField()

    class Meta:
        model = Post
        fields = [
            "id",
            "category",
            "body",
            "images",
            "video",
            "ratings",
            "total_ratings",
            "average_rating",
            "published",
            "created_at",
            "updated_at",
        ]
        depth = 1

    def get_total_ratings(self, obj):
        return obj.ratings.count()

    def get_average_rating(self, obj):
        total = obj.ratings.aggregate(Sum('score'))['score__sum']
        if total:
            return total / obj.ratings.count()
        return 0


    #rest of your logic and code 
  • Related