Home > Mobile >  DRF Add annotated field to nested serializer
DRF Add annotated field to nested serializer

Time:07-12

I have two serializers that represent comments and their nested comments. I'm provide a queryset to viewset with annotated field likes. But my problem is that field only working in parent serializer. When i add this field to nested serializer i got error

Got AttributeError when attempting to get a value for field likes on serializer CommentChildrenSerializer. The serializer field might be named incorrectly and not match any attribute or key on the Comment instance. Original exception text was: 'Comment' object has no attribute 'likes'.

Here is some my code. Thanks

Models.py

class Post(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    slug = models.SlugField(blank=True)
    body = models.TextField()
    tags = TaggableManager(blank=True)
    pub_date = models.DateTimeField(auto_now_add=True)


    class Meta:
        ordering = ['-pub_date']


class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE,
                             related_name='comments')
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    text = models.TextField(max_length=500)
    pub_date = models.DateTimeField(auto_now=True)
    parent = models.ForeignKey('self', blank=True, null=True,
                               on_delete=models.CASCADE, related_name='children')

    class Meta:
        ordering = ['-pub_date']


class Vote(models.Model):
    comment = models.ForeignKey(Comment, on_delete=models.CASCADE,
                                related_name='votes')
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    choice = models.BooleanField(null=True)

Serializers.py

class PostRetrieveSerializer(PostSerializer):
    comments = CommentSerializer(read_only=True, many=True)
    author = AuthorInfoSerializer(serializers.ModelSerializer)

    class Meta:
        model = Post
        fields = ['id', 'author', 'slug', 'title', 'body', 'tags', 'pub_date', 'comments']


class CommentChildrenSerializer(serializers.ModelSerializer):
    author = AuthorInfoSerializer(read_only=True)
    likes = serializers.IntegerField()

    class Meta:
        model = Comment
        fields = ['author', 'id', 'text', 'pub_date', 'parent', 'likes']


class CommentSerializer(serializers.ModelSerializer):
    author = AuthorInfoSerializer(read_only=True)
    children = CommentChildrenSerializer(many=True)
    likes = serializers.IntegerField()

    class Meta:
        ordering = ['pub_date']
        model = Comment
        fields = ['author', 'id', 'text', 'pub_date', 'children', 'likes']

Views.py

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all().prefetch_related(
        Prefetch('comments', queryset=Comment.objects.filter(parent__isnull=True)
                 .annotate(likes=Count('votes__choice'))))
    serializer_class = PostSerializer
    permission_classes = [IsOwnerOrAdminOrReadOnly]
    pagination_class = PostPagination
    lookup_field = 'slug'


    def get_serializer_class(self):
        """"
        Attach related comments
        when get post detail
        """
        if self.action == 'retrieve':
            return PostRetrieveSerializer
        return self.serializer_class

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

CodePudding user response:

maybe you can do something like this, adding the like field in each child comment.

queryset = Post.objects.all()\
    .prefetch_related(
        Prefetch(
            'comments',
             queryset=Comment.objects\
                 .filter(parent__isnull=True)\
                 .annotate(likes=Count('votes__choice'))\
                 .prefetch_related(
                    'children',
                    queryset=Comments.objects.all()\
                    .annotate(likes=Count('votes__choice'))
                 )
        )
    )

I hope this help you. Regards!

CodePudding user response:

On your model level, add a custom property like the below.

class Comment(models.Model):
    ...

    class Meta:
        ordering = ['-pub_date']

    @property
    def likes(self):
        return self.votes.count()

On your serializer add SerializerMethodField

class CommentChildrenSerializer(serializers.ModelSerializer):
    author = AuthorInfoSerializer(read_only=True)
    likes = serializers.SerializerMethodField() # Change here

    class Meta:
        model = Comment
        fields = ['author', 'id', 'text', 'pub_date', 'parent', 'likes']
    
    # method for the SerializerMethodField
    def get_likes(self, obj):
        return obj.likes

Update both Comment related serializers. I believe this approach is simpler than the current implementation.

  • Related