此前我们一直在操作博客文章(Post)资源,并借此介绍了序列化器(Serializer)、视图集(Viewset)、路由器(Router)等 django-rest-framework 提供的便利工具,借助这些工具,就可以非常快速地完成 RESTful API 的开发。
评论(Comment)是另一种资源,我们同样借助以上工具来完成对评论资源的接口开发。
首先是设计评论 API 的 URL,根据 RESTful API 的设计规范,评论资源的 URL 设计为:/comments/
对评论资源的操作有获取某篇文章下的评论列表和创建评论两种操作,因此相应的 HTTP 请求和动作(action)对应如下:
HTTP请求 | Action | URL |
---|---|---|
GET | list_comments | /posts/:id/comments/ |
POST | create | /comments/ |
文章评论列表 API 使用自定义的 action,放在 /post/ 接口的视图集下;发表评论接口使用标准的 create action,需要定义单独的视图集。
然后需要一个序列化器,用于评论资源的序列化(获取评论时),反序列化(创建评论时)。有了编写文章序列化器的基础,评论序列化器就是依葫芦画瓢的事。
comments/serializers.py
from rest_framework import serializers
from .models import Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = [
"name",
"email",
"url",
"text",
"created_time",
"post",
]
read_only_fields = [
"created_time",
]
extra_kwargs = {"post": {"write_only": True}}
注意这里我们在 Meta
中增加了 read_only_fields
、extra_kwargs
的声明。
read_only_fields
用于指定只读字段的列表,由于 created_time
是自动生成的,用于记录评论发布时间,因此声明为只读的,不允许通过接口进行修改。
extra_kwargs
指定传入每个序列化字段的额外参数,这里给 post
序列化字段传入了 write_only
关键字参数,这样就将 post 声明为只写的字段,这样 post 字段的值仅在创建评论时需要。而在返回的资源中,post 字段就不会出现。
首先来实现创建评论的接口,先为评论创建一个视图集:
comments/views.py
from rest_framework import mixins, viewsets
from .models import Comment
from .serializers import CommentSerializer
class CommentViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = CommentSerializer
def get_queryset(self):
return Comment.objects.all()
视图集非常的简单,混入 CreateModelMixin
后,视图集就实现了标准的 create action。其实 create action 方法的实现也非常简单,我们来学习一下 CreateModelMixin
的源码实现。
class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
核心逻辑在 create
方法:首先取到绑定了用户提交数据的序列化器,用于反序列化。接着调用 is_valid
方法校验数据合法性,如果不合法,会直接抛出异常(raise_exception=True
)。否则就执行序列化的 save
逻辑将评论数据存入数据库,最后返回响应。
接着在 router 里注册 CommentViewSet
视图集:
router.register(r"comments", comments.views.CommentViewSet, basename="comment")
进入 API 交互后台,可以看到首页列出了 comments 接口的 URL,点击进入 /comments/ 后可以看到一个评论表单,在这里可以提交评论数据与创建评论的接口进行交互。
接下来实现获取评论列表的接口。通常情况下,我们都是只获取某篇博客文章下的评论列表,因此我们的 API 设计成了 /posts/:id/comments/。这个接口具有很强的语义,非常符合 RESTful API 的设计规范。
由于接口位于 /posts/ 空间下,因此我们在 PostViewSet
添加自定义 action 来实现,先来看代码:
blog/views.py
class PostViewSet(
mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
# ...
@action(
methods=["GET"],
detail=True,
url_path="comments",
url_name="comment",
pagination_class=LimitOffsetPagination,
serializer_class=CommentSerializer,
)
def list_comments(self, request, *args, **kwargs):
# 根据 URL 传入的参数值(文章 id)获取到博客文章记录
post = self.get_object()
# 获取文章下关联的全部评论
queryset = post.comment_set.all().order_by("-created_time")
# 对评论列表进行分页,根据 URL 传入的参数获取指定页的评论
page = self.paginate_queryset(queryset)
# 序列化评论
serializer = self.get_serializer(page, many=True)
# 返回分页后的评论列表
return self.get_paginated_response(serializer.data)
action
装饰器我们在上一篇教程中进行了详细说明,这里我们再一次接触到 action
装饰器更为深入的用法,可以看到我们除了设置 methods
、detail
、url_path
这些参数外,还通过设置 pagination_class
、serializer_class
来覆盖原本在 PostViewSet
中设置的这些类属性的值(例如对于分页,PostViewSet
默认为我们之前设置的 PageNumberPagination
,而这里我们替换为 LimitOffsetPagination
)。
list_comments
方法逻辑非常清晰,注释中给出了详细的说明。另外还可以看到我们调用了一些辅助方法,例如 paginate_queryset
对查询集进行分页;get_paginated_response
返回分页后的 HTTP 响应,这些方法其实都是 GenericViewSet
提供的通用辅助方法,源码也并不复杂,如果不用这些方法,我们自己也可以轻松实现,但既然 django-rest-framework 已经为我们写好了,直接复用就行,具体的实现请大家通过阅读源码进行学习。
现在进入 API 交互后台,进入某篇文章的详细接口,例如访问 /api/posts/5/,Extra Actions 下拉框中可以看到 List comments 的选项:
点击 List comments 即可进入这篇文章下的评论列表接口,获取这篇文章的评论列表资源了:
-- EOF --
djangorestframework==3.13.1 使用原来的获取序列化容器获取不到,只好强行指定了
提示错误:get_serializer_class() got an unexpected keyword argument 'instance';把serializer = self.get_serializer(instance=page, many=True) 改成serializer = CommentSerializer(instance=page, many=True)后正常,请问博主是什么原因,多谢
rest版本不一致吧,作者是3.11.1
多级评论功能怎么实现啊?
博客代码开源的,实现方式可以参考里面的 comments app 代码。