Search code examples
django-rest-frameworkdjango-serializer

Add annotated value in json response (django rest)?


I got a model that looks like this:

class Book(models.Model):
    author = models.CharField(max_length=100)
    chapters = models.IntegerField()
    name = models.CharField(max_length=100)

and a "grand child" linked together through a Chapter model:

class Verse(models.Model):
    chapter = models.ForeignKey(Chapter, on_delete=CASCADE)
    verse = models.TextField()
    verse_number = models.IntegerField()

I wish to get the count of all verses that belongs to a book, and I'm fetching them like this:

Book.objects.annotate(Count('chapters', distinct=True), total_num_verses=Count('chapter__verse', distinct=True))

However, I'm not sure how to get this into my serializer. I was thinking of using a SerializerMethodField The goal is to get the total_num_verses as a key/value pair in my json response

class BookSerializer(serializers.ModelSerializer):
    total_verse_count = serializers.SerializerMethodField()

    class Meta:
        model = Book
        fields = "__all__"

    # this doesn't work..
    def get_total_verse_count(self, obj):
        print(obj.get_verses)
        return self.annotate(Count('chapters', distinct=True), total_num_verses=Count('chapter__verse', distinct=True))

I just get 'BookSerializer' object has no attribute 'annotate'

Should I make a @property method in the Book model class itself?

views.py is just a plain APIView

class BookAPIView(APIView):
    """
    List all books
    """

    def get(self, request, format=None):
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data)

Solution

  • An effective way to do that is adding a readonly=True field in the serializer:

    class BookSerializer(serializers.ModelSerializer):
        total_verse_count = serializers.IntegerField(read_only=True)
    
        class Meta:
            model = Book
            fields = ['author', 'chapters', 'name', 'total_verse_count']

    In the view, you then pass the annotated queryset to the serializer:

    class BookAPIView(APIView):
    
        def get(self, request, format=None):
            books = Book.objects.annotate(
                total_verse_count=Count('chapter__verse')
            )
            serializer = BookSerializer(books, many=True)
            return Response(serializer.data)