DRF Добавление аннотированного поля во вложенный сериализатор
У меня есть два сериализатора, которые представляют комментарии и вложенные в них комментарии. Я предоставляю кверисет в viewset с аннотированным полем likes. Но моя проблема в том, что поле работает только в родительском сериализаторе. Когда я добавляю это поле во вложенный сериализатор, я получаю ошибку
Получили ошибку AttributeError при попытке получить значение для поля
likes
на сериализатореCommentChildrenSerializer
. Поле сериализатора может быть названо неверно и не совпадать с атрибутом или ключом экземпляраComment
. Оригинальный текст исключения был следующим: объект 'Comment' не имеет атрибута 'likes'.
Вот мой код. Спасибо
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)
возможно, вы можете сделать что-то вроде этого, добавив поле like в каждый дочерний комментарий.
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'))
)
)
)
Надеюсь, это поможет вам. С уважением!
На уровне модели добавьте пользовательское свойство, как показано ниже.
class Comment(models.Model):
...
class Meta:
ordering = ['-pub_date']
@property
def likes(self):
return self.votes.count()
В вашем сериализаторе добавьте 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
Обновите оба сериализатора, связанных с комментариями. Я считаю, что этот подход проще, чем текущая реализация.