前两篇:Django 学习笔记 | Dango REST Framework 学习笔记
文档对于后端开发是相当重要,即使是一个人写前后端,如果不写文档,可能前天写的接口今天又不知道了。而且,测试部分是基于文档进行编写的,上过软件工程课程的同学应该都有了解。足以看出文档对后端开发的重要性。
对于 Django REST Framework,也有文档生成的工具。django-rest-swagger 就是一款,但是它已经不支持 Django 3.0 了。Django 3.12 可以改用 drf-yasg: Django REST Framwork Yet Another Swagger Generator。
GitHub | Read the Docs | 我使用 drf-ysag 编写的开源项目
drf-yasg 安装及全局配置
安装以及这个官方文档非常详细的描述了,我就不多说了。
配置好并运行 Django 项目以后,就可以使用浏览器访问 /swagger/
和 /redoc/
(链接取决于你的 urls
的配置)看到两种风格的文档了。我个人比较喜欢 Swagger 的,所以下面均用 Swagger 文档的界面作截图。
另外,我个人更喜欢在 Parameters 和 Response 的地方默认展示 Example Value
而不是 Model
,所以各位可以在 <project_name>/settings.py
中添加下面几行:
1 2 3
| SWAGGER_SETTINGS = { 'DEFAULT_MODEL_RENDERING': 'example' }
|
drf-yasg 使用方法
但是 drf-yasg 的使用方法并不详细。。。。
于是继续 Google 看到了另外一篇文章:自定義 drf-yasg 的 Swagger 文檔— 以 GET、POST、檔案上傳為例,简单了解了一下。
之后再去怼文档,了解了更多的方法。
为 View 的文档添加摘要和说明
Swagger 的方法是使用 drf_yasg.utils.swagger_auto_schema
作为装饰器修饰每个视图。
- 可以装饰 Django 风格的
View
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from drf_yasg.utils import swagger_auto_schema
@swagger_auto_schema( method='POST', operation_summary='注册新用户', operation_description='成功返回 201\n' '失败(参数错误或不符合要求)返回 400', ) @api_view(['POST']) def signup(request: WSGIRequest) -> Response: register_serializer = UserRegisterSerializer(data=request.data) if register_serializer.is_valid(): u = register_serializer.save() user_serializer = UserSerializer(u) return Response(user_serializer.data, status=status.HTTP_201_CREATED) return Response(register_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
- 也可以对
APIView
的 post
get
等函数进行装饰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class ActivityCheckInView(APIView): @swagger_auto_schema( operation_summary='用户签到', operation_description='可能会有以下情况:\n' '1. 签到成功,用户经验+10,每位管理员经验+50,返回 200\n' '2. 活动不存在,返回 404\n' '3. POST 数据不包含签到码,返回 400\n' '4. 演讲者关闭了签到,返回 403\n' '5. 签到码错误,返回 403\n' '注:要求登录,否则返回 403', request_body=Schema_object(Schema_check_in_code), responses={201: None, 403: Schema_object(Schema_detail)} ) def post(self, request: WSGIRequest, id: int) -> Response: if not Activity.objects.filter(id=id): return Response({"detail": "活动不存在"}, status=status.HTTP_404_NOT_FOUND) activity = Activity.objects.get(id=id) if "check_in_code" not in request.POST: return Response({"detail": "POST 数据不包含签到码"}, status=status.HTTP_400_BAD_REQUEST) if activity.datetime.date() != datetime.now().date(): return Response({"detail": "非当日活动"}, status=status.HTTP_403_FORBIDDEN) if not activity.check_in_open: return Response({"detail": "演讲者已关闭签到"}, status=status.HTTP_403_FORBIDDEN) if request.POST["check_in_code"] != activity.check_in_code: return Response({"detail": "签到码错误"}, status=status.HTTP_403_FORBIDDEN)
activity.attender.add(request.user)
return Response(status=status.HTTP_200_OK)
|
- 还可以对
GeneticAPIView
进行装饰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| from django.utils.decorators import method_decorator
@method_decorator(name="get", decorator=swagger_auto_schema( operation_summary='获取沙龙信息', operation_description='获取沙龙信息\n' '注:需要登录', )) @method_decorator(name="put", decorator=swagger_auto_schema( operation_summary='更新沙龙信息', operation_description='应答和 PATCH 方法相同,但 PUT 要求在请求中提交所有信息,不推荐使用', )) @method_decorator(name="patch", decorator=swagger_auto_schema( operation_summary='更新沙龙部分信息', operation_description='更新沙龙信息,成功返回 200\n' '如沙龙不存在,返回 404\n' '如更新值不合法,返回 400\n' '注:更新参与者名单请使用 `/users/check_in/` 或 `/users/check_in_admin/` 接口\n' '注:需要是沙龙演讲者或管理员,否则返回 403\n' '注:PATCH 方法可以只提交更新的值,也可以提交所有值', )) @method_decorator(name="delete", decorator=swagger_auto_schema( operation_summary='删除沙龙', operation_description='删除沙龙,成功返回 204\n' '注:需要是沙龙演讲者或管理员,否则返回 403', )) class ActivityDetailView(RetrieveUpdateDestroyAPIView): permission_classes = (IsAuthenticated, IsPresenterOrAdminOrReadOnly, ) queryset = Activity.objects.all() serializer_class = ActivitySerializer
|
ActivitySerializer 的定义可见 GitHub。
可能有的读者应该已经注意到了,对于 GeneticAPIView,Swagger 能够自动捕获其 Serializer
以及 ModelSerializer
并作为参数!除此之外,它还可以正确识别各 Serializer
类中的read_only_fields
,并在请求的 Parameters 中去掉!真是太香了!
使用 Serializer 为 View 的文档添加 parameter 和 response
当然,对于 APIView 和 Django View,也是可以在 swagger_auto_schema
手动设置参数、返回值的。而如果对 GeneticAPIView 设置后,就会覆盖原来的设置。
在后文,我们就不讨论修饰哪一种 View,只讨论 swagger_auto_schema
这个装饰器了。
而后面的大部分讨论,都会围绕这个这个函数的文档。
对于 POST、PATCH 的放在 request_body
里的数据格式,可以放在 request_body
的参数部分,这个参数可以接收 rest_framework.Serializer
双厨狂喜,或是 openapi.Schema
。
response
的参数也是类似,只是变成了一个 dict,因为每种 response body
应当对应一个 HTTP 状态码。
对于 rest_framework.Serializer
和 openapi.Schema
的用法,先从熟悉的 rest_framework.Serializer
说起。刚才在 GeneticAPIView 中,相信各位应该已经猜到了,它会默认将 GeneticAPIView 中的 serializer_class
作为 request_body
和 response
。当然,我们也可以手动设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @swagger_auto_schema( method='POST', operation_summary='注册新用户', operation_description='成功返回 201\n' '失败(参数错误或不符合要求)返回 400', request_body=UserRegisterSerializer, responses={201: UserSerializer()} ) @api_view(['POST']) def signup(request: WSGIRequest) -> Response: register_serializer = UserRegisterSerializer(data=request.data) if register_serializer.is_valid(): u = register_serializer.save() user_serializer = UserSerializer(u) return Response(user_serializer.data, status=status.HTTP_201_CREATED) return Response(register_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
两个 Serializer 的定义可见 GitHub。
绕开 IDE 对 {200: None} 的错误检测
文档中提到,如果 responses
参数中没有 2xx
类的,会自动添加一个 200
(对于 GET
PUT
PATCH
),或是 201
(对于 POST
)或 204
(对于 DELETE
)。要想屏蔽这个 2xx
,只需要添加 {200: None}
。
这个 None 有用,但是并不能通过 Pycharm 的类型检测(强迫症震怒):
作为强迫症,可以定义一个 Schema_none = None
,然后:
使用 Schema 为 View 的文档添加 parameter 和 response
如果没有使用 Serializers 而是手动处理的,也可以手动编写 Schema。
1 2 3 4 5 6 7 8 9 10 11 12
| @swagger_auto_schema( method='POST', operation_summary='登录', operation_description='成功返回 200' '失败(账户或密码错误)返回 401\n' '注:一个已登录的用户 A 尝试 login 账户 B 失败后,仍具有账户 A 的凭证。', request_body=openapi.Schema(type=openapi.TYPE_OBJECT, properties={ "email": openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_EMAIL, description='邮箱'), "password": openapi.Schema(type=openapi.TYPE_STRING, description='密码') }), responses={200: Schema_None} )
|
openapi.Schema
的 type
和 format
就不用多说了,IDE 输入 openapi.TYPE_
和 openapi.FORMAT_
就能提示,也可以去看看文档。
emmm 文档变好看了,但是感觉代码变得又长又丑了
因此,我创建了一个 swagger.py
专门存放这些 Schema,核心代码里面直接调用就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
def Schema_array(schema: openapi.Schema) -> openapi.Schema: return openapi.Schema(type=openapi.TYPE_ARRAY, items=schema)
def Schema_object(*props: dict) -> openapi.Schema: result_properties = {} for prop in props: result_properties = {**prop, **result_properties} return openapi.Schema(type=openapi.TYPE_OBJECT, properties=result_properties)
Schema_None = None Schema_email = {"email": openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_EMAIL, description='邮箱')} Schema_password = {"password": openapi.Schema(type=openapi.TYPE_STRING, description='密码')}
|
核心代码:
1 2 3 4 5 6 7 8 9
| @swagger_auto_schema( method='POST', operation_summary='登录', operation_description='成功返回 200\n' '失败(账户或密码错误)返回 401\n' '注:一个已登录的用户 A 尝试 login 账户 B 失败后,仍具有账户 A 的凭证。', request_body=Schema_object(Schema_email, Schema_password), responses={200: Schema_None} )
|
效果图和上面相同。简洁多了。
另一个用到 Array 的例子:
1 2 3 4 5
| Schema_title = {"title": openapi.Schema(type=openapi.TYPE_STRING, description='沙龙标题')} Schema_datetime = { "datetime": openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_DATETIME, description="日期时间")} Schema_location = {"location": openapi.Schema(type=openapi.TYPE_STRING, description='地点')} Schema_presenter_ids = {"presenter": Schema_array(Schema_object(Schema_id))}
|
1 2 3 4 5 6 7 8 9
| @method_decorator(name='post', decorator=swagger_auto_schema( operation_summary='创建沙龙', operation_description='成功返回 201,参数错误返回 400', request_body=Schema_object(Schema_title, Schema_datetime, Schema_location, Schema_presenter_ids), responses={201: ActivitySerializer()} ))
|
有了 Array 和 Object,就可以构造所有 JSON 语法了。