如何在Django Rest Framework中序列化嵌套层级评论
Django Rest Framework 实现评论嵌套层级结构
需求说明
当前API返回平级评论列表,示例如下:
"results": [ { "id": 12, "blog_slug": "rest-api-django", "comment": "Parent Comment", "main_comment_id": null }, { "id": 13, "blog_slug": "rest-api-django", "comment": "Child Comment of 12", "main_comment_id": 12 }, { "id": 14, "blog_slug": "rest-api-django", "name": "Mr Ahmed", "comment": "Child Comment OF 13", "comment_uuid": "c0b389bc-3d4a-4bda-b6c6-8d3ef494c93c", "main_comment_id": 13 } ]
期望改造为子评论嵌套在对应父评论children字段下的层级结构,示例如下:
{ "results": [ { "id": 12, "blog_slug": "rest-api-django", "comment": "Parent Comment", "main_comment_id": null, "children": [ { "id": 13, "blog_slug": "rest-api-django", "comment": "Child Comment of 12", "main_comment_id": 12, "children": [ { "id": 14, "blog_slug": "rest-api-django", "name": "Mr Ahmed", "comment": "Child Comment OF 13", "comment_uuid": "c0b389bc-3d4a-4bda-b6c6-8d3ef494c93c", "main_comment_id": 13 } ] } ] } ] }
现有代码
模型代码
class BlogComment(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,blank=True,null=True) blog_slug = models.CharField(max_length=500) main_comment_id = models.ForeignKey("self",on_delete=models.CASCADE,blank=True,null=True) name = models.CharField(max_length=250) email = models.CharField(max_length=250) comment = models.TextField() is_published = models.BooleanField(True) comment_uuid = models.CharField(max_length=250)
序列化器代码
class BlogCommentSerializers(serializers.Serializer): id = serializers.IntegerField(read_only=True) comment_uuid = serializers.CharField(read_only=True) blog_slug = serializers.CharField(max_length=500) main_comment_id = serializers.CharField(required=False) name = serializers.CharField(max_length=250) email = serializers.CharField(max_length=250) comment = serializers.CharField(style={'base_template': 'textarea.html'},allow_blank=False,required=True) is_published = serializers.BooleanField(default=True) def to_representation(self, instance): data = { 'id': instance.id, 'blog_slug': instance.blog_slug, 'name': instance.name, 'email': instance.email, 'comment': instance.comment, "comment_uuid": instance.comment_uuid, "main_comment_id": instance.main_comment_id } if instance.main_comment_id: data['main_comment_id'] = instance.main_comment_id.id # Change to the ID number return data
API视图代码
if request.method == 'GET': paginator = PageNumberPagination() paginator.page_size = 1 blogs = Blog.objects.filter(is_approved=True).all() result_page = paginator.paginate_queryset(blogs, request) serializer = BlogSerialzers(result_page, many=True) paginated_response = paginator.get_paginated_response(serializer.data) if paginated_response.get('next'): paginated_response.data['next'] = paginated_response.get('next') if paginated_response.get('previous'): paginated_response.data['previous'] = paginated_response.get('previous') return paginated_response
实现步骤
1. 给模型添加反向关联名称
修改BlogComment模型的main_comment_id字段,添加related_name方便查询子评论:
main_comment_id = models.ForeignKey("self", on_delete=models.CASCADE, blank=True, null=True, related_name='children')
2. 修改序列化器实现嵌套
改用ModelSerializer简化代码,添加递归嵌套的子评论字段:
class BlogCommentSerializers(serializers.ModelSerializer): # 递归序列化子评论 children = serializers.SerializerMethodField() main_comment_id = serializers.IntegerField(allow_null=True, required=False) class Meta: model = BlogComment fields = ['id', 'comment_uuid', 'blog_slug', 'main_comment_id', 'name', 'email', 'comment', 'is_published', 'children'] def get_children(self, obj): # 获取当前评论的已发布子评论,递归序列化 child_comments = obj.children.filter(is_published=True) return BlogCommentSerializers(child_comments, many=True, context=self.context).data def to_representation(self, instance): data = super().to_representation(instance) # 处理main_comment_id为null的情况 if not data['main_comment_id']: data['main_comment_id'] = None return data
3. 调整视图返回层级评论
如果是单篇博客的评论接口,修改视图只返回顶级评论(main_comment_id为null):
def blog_comments(request, blog_slug): if request.method == 'GET': # 获取当前博客下的已发布顶级评论 top_comments = BlogComment.objects.filter( blog_slug=blog_slug, main_comment_id__isnull=True, is_published=True ).order_by('-id') paginator = PageNumberPagination() paginator.page_size = 10 result_page = paginator.paginate_queryset(top_comments, request) serializer = BlogCommentSerializers(result_page, many=True) return paginator.get_paginated_response(serializer.data)
如果要在博客列表接口中包含评论,修改BlogSerialzers添加评论字段:
class BlogSerialzers(serializers.ModelSerializer): comments = serializers.SerializerMethodField() class Meta: model = Blog fields = ['id', 'slug', 'title', 'comments'] # 补充其他博客字段 def get_comments(self, obj): top_comments = BlogComment.objects.filter( blog_slug=obj.slug, main_comment_id__isnull=True, is_published=True ) return BlogCommentSerializers(top_comments, many=True).data
注意事项
- 递归序列化层级过深会影响性能,可在
get_children中添加深度限制,或预查询所有评论后在内存中构建层级结构。 - 始终过滤
is_published=True的评论,避免返回未发布内容。
内容的提问来源于stack exchange,提问作者boyenec
相关产品推荐
相关产品推荐

