Search code examples
djangodjango-modelsdjango-rest-frameworkdjango-ormdjango-serializer

DRF Get likes and dislikes grouped by day


I have a models named Post and Like. How can i get json with ount of likes and dislikes grouped by date (date field in Like model)?

Here is my models.py

class Post(models.Model):
    """Post model"""
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=255)
    body = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title


class Like(models.Model):
    """Like model"""
    LIKE = (
        ('like', 'like'),
        ('dislike', 'dislike')
    )

    user = models.ForeignKey(User, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='likes')
    like = models.CharField(max_length=255, choices=LIKE)
    date = models.DateField(auto_now=True)

Here is my serializers.py:

class AnaliticsSerializer(serializers.ModelSerializer):
    """Like analitic"""

    class Meta:
        model = Like
        fields = '__all__'

Here is my vievs.py:

class AnaliticView(ListAPIView):
    queryset = Like.objects.all()
    serializer_class = AnaliticsSerializer
    filter_backends = [DjangoFilterBackend]
    filter_fields = ['date']

result what i want

[
  {
    "date": "2020-11-14",
    "total_likes": 25,
    "total_dislikes": 17
  },
  {
    "date": "2020-11-15",
    "total_likes": 38,
    "total_dislikes": 8
  },
  {
    "date": "2020-11-18",
    "total_likes": 11,
    "total_dislikes": 0
  }


Solution

  • Here's a working example of one basic approach to this. It should give you the idea.

    The analytics code shouldn't really be in the view. Also, some of the grouping and counting might be offloaded to the database, using advanced ORM querying like this.

    views.py

    from collections import Counter
    from datetime import datetime, timedelta
    from itertools import groupby
    
    from django_filters import rest_framework as filters
    from rest_framework.generics import GenericAPIView
    from rest_framework.response import Response
    
    from likes.filters import DateRangeFilterSet
    from likes.models import Like
    
    
    class AnaliticView(GenericAPIView):
        queryset = Like.objects.all()
        filter_backends = (filters.DjangoFilterBackend,)
        filterset_class = DateRangeFilterSet
    
        def get(self, request, format=None):
            queryset = self.get_queryset()
            filtered_queryset = self.filter_queryset(queryset)
    
            # Queryset needs to be ordered by date for groupby to work correctly
            ordered_queryset = filtered_queryset.order_by('date')
            likes_by_date = groupby(ordered_queryset,
                                    lambda like: like.date.strftime("%Y-%m-%d"))
    
            analytics = []
            for date, likes in likes_by_date:
                count = Counter(like.like for like in likes)
                analytics.append(
                    {
                        'date': date,
                        'total_likes': count['like'],
                        'total_dislikes': count['dislike'],
    
                    }
                )
    
            return Response(analytics)                                  
    

    Like I said in the comments, it would be possible to create a lightweight class with attributes for date and the two totals, and pass a list of instances of that to a serializer to get the response data. In my opinion, that's overkill as you can just build a dictionary that is easily serialised into JSON.

    Update:

    I've switched to a GenericAPIView, which is a superclass of ListAPIView, because it supports filter backends. I have added a FilterSet that filters by date_from and date_to:

    filters.py

    from django_filters import rest_framework as filters
    
    from likes import models
    
    
    class DateRangeFilterSet(filters.FilterSet):
        date_from = filters.DateFilter(field_name='date', lookup_expr='gte')
        date_to = filters.DateFilter(field_name='date', lookup_expr='lte')
    
        class Meta:
            model = models.Like
            fields = ('date_from', 'date_to')