Django Rest API- use prefetch_related with ModelSerializer

I have a comment field for every blog post. I want to pass Comment.objects.all() from Views.py to ModelSerializer def get_comments(self, obj) to reduce the number of sql queries. As I am serializing a list of blog posts

Views.py

class BlogViewSet(ModelViewSet):
    queryset = Blog.objects.all().annotate(
        author_name=F('author__username')
    )
    serializer_class = BlogSerializer
    permission_classes = [IsOwnerOrReadOnly]

        def list(self, request):
            return Response({'blogs': BlogSerializer(self.queryset, many=True).data})

Serializers.py

class BlogSerializer(ModelSerializer):
    author_name = serializers.CharField(read_only=True)
    comments = SerializerMethodField()

    class Meta:
        model = Blog
        fields = ('title_text', 'main_text', 'datepublished', 'author_name', 'id', 'comments')


    def get_comments(self, obj):
        # filter comment
        comment_object = Comment.objects.filter(post_id=obj.id)
        comments = CommentSerializer(comment_object, many=True).data
        return comments

Answer

You don’t have to pass anything from the View.

  • First, you have to change the comments field in your BlogSerializer.
class CommentSerializer(ModelSerializer):
    # This serializer should have all the details of your comments.
    ....
    class Meta:
        model = Comment
        fields = "__all__" # Or whatever fields you want to set.

class BlogSerializer(ModelSerializer):
    author_name = serializers.CharField(read_only=True)
    comments = CommentSerializer(many=True, read_only=True) # I am not sure of the source of your comment reverse manager name

    class Meta:
        model = Blog
        fields = ('title_text', 'main_text', 'datepublished', 'author_name', 'id', 'comments')
  • Second, you have to make a small change to your view’s queryset in order to reduce the number of queries sent to the database by using prefetch_related
class BlogViewSet(ModelViewSet):
    queryset = Blog.objects.prefetch_related('comments').all().annotate(
        author_name=F('author__username')
    )
    serializer_class = BlogSerializer
    permission_classes = [IsOwnerOrReadOnly]

        def list(self, request):
            return Response({'blogs': BlogSerializer(self.get_queryset(), many=True).data})

I assumed in the code snippets that you didn’t set a related_name on your Blog ForeignKey for the Comment model, so by default, its related manager on Blog will be comment_set

Update

Your models should look like this, in order for this solution to work

class Comment(models.Model):
    ...
    blog = models.ForeignKey('Blog', on_delete=models.CASCADE, related_name='comments')
    ...

class Blog(models.Model):
    # comment = models.ForeignKey(Comment, on_delete=models.CASCADE, null=True) 
    # this foreign key shouldn't be here, remove it.

    ....

Do the changes on Serializer and View

# in BlogSerializer
    comments = CommentSerializer(many=True, read_only=True)
# In BlogViewSet
    queryset = Blog.objects.prefetch_related('comments').all().annotate(
        author_name=F('author__username')
    )