Andrei Sima
Andrei Sima

Reputation: 159

Django Rest Framework Star Rating System

I have the following structure in my Django models.

ratings.models.py

class Rating(models.Model):
    class Meta:
        verbose_name = 'Rating'
        verbose_name_plural = 'Ratings'
        unique_together = [
            'user',
            'product']
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='user')
    product = models.ForeignKey(
        Product,
        on_delete=models.CASCADE,
        related_name='product')
    rating = models.PositiveIntegerField(
        null=False,
        blank=False)

Product has nothing special to show here. Just a simple Django model. I will omit it.

In ProductDetailView in serializers i need the count per rating. A product can be rated from 1 to 5. Example:

{
...
"ratings": {
  "rating1_star": 6,
  "rating2_star": 5,
  "rating3_star": 4,
  "rating4_star": 3,
  "rating5_star": 3
}

I have achieved this with the following code.

class ProductDetailSerializer(serializers.ModelSerializer):
    ratings = serializers.SerializerMethodField('get_ratings_detail')

    class Meta:
        model = Product
        fields = [...,'ratings',]

    def get_ratings_detail(self, obj):
        ratings = Rating.objects.filter(
            product=obj)
        r_details = ratings.aggregate(
            rating1=Count(
                'product',
                filter=Q(rating__iexact=1)),
            rating2=Count(
                'product',
                filter=Q(rating__iexact=2)),
            rating3=Count(
                'product',
                filter=Q(rating__iexact=3)),
            rating4=Count(
                'product',
                filter=Q(rating__iexact=4)),
            rating5=Count(
                'product',
                filter=Q(rating__iexact=5)),
        )
        return r_details

My question is if can i achieve the same result using a better method? I quite do not like how get_ratings_detail looks like, and also i am not sure if this is the best way to do it. Thank you.

Upvotes: 0

Views: 1564

Answers (1)

Sayse
Sayse

Reputation: 43310

I'd just get the ratings values, use a counter, and then parse that out as required...

from collections import Counter
ratings = Counter(ratings.values_list("rating", flat=True))

For example,

>>> {f"rating{star}_star": count for star, count in ratings.items()} 
{'rating5_star': 1, 'rating2_star': 2, 'rating3_star': 2, 'rating4_star': 1, 'rating1_star': 1}

Upvotes: 1

Related Questions