告别手写api 续

restful doc

由于不死心,继续研究了下怎么比较方便的补充schema。

4天前发现了django-rest-framework更新到了3.7版本
upload successful

3.7更新了自定义schema的方法

http://www.django-rest-framework.org/topics/3.7-announcement/#customizing-api-docs-schema-generation

那么就可以直接用rest自带的doc,然后再补充schema就完成了api文档的生成。

upload successful
界面看着很舒服,以后不用再改完api再改文档了,哈哈哈哈。

自定义schema

一些需要注意的地方

官方文档很简单,但在尝试过程过也踩了一些坑。

装饰器

对于@api_view之类的视图可以这么补充

1
2
3
4
5
6
7
8
@api_view(['GET'])
@permission_classes([permissions.IsAuthenticated])
@schema(AutoSchema(
manual_fields=[coreapi.Field('q', location='query', schema=coreschema.String(description='search value')),
coreapi.Field('page', location='query', schema=coreschema.Integer(description='page',)), ]))
def search(request):
# some code...

description问题

添加描述有2种方法

  • 方法一:

    1
    2
    3
    schema = AutoSchema(manual_fields=[
    coreapi.Field('search', location='query',description='search',schema=coreschema.String())
    ])
  • 方法二:

    1
    2
    3
    schema = AutoSchema(manual_fields=[
    coreapi.Field('search', location='query',schema=coreschema.String(description='search value'))
    ])

方法一
在swagger里面显示没啥问题,但是restful自带的doc就不会显示。

方法二
比较通用,swagger和doc都会显示字段的描述。

编码问题

中文编码会报错,这时候加u就能正常。

1
2
schema = AutoSchema(manual_fields=[
      coreapi.Field('search', location='query',schema=coreschema.String(description=u'搜索'))

serializer添加描述

实现很简单,在模型中添加help_text来补充

markdown

对于函数其实都可以书写markdown格式的注释,这也很方便的对文档进行更详细的补充

https://github.com/encode/django-rest-framework/blob/master/tests/test_description.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def test_view_description_uses_docstring(self):
"""Ensure view descriptions are based on the docstring."""
class MockView(APIView):
"""an example docstring
====================
* list
* list
another header
--------------
code block
indented
# hash style header #
@@ json @@
[{
"alpha": 1,
"beta: "this is a string"
}]
@@"""
assert MockView().get_view_description() == DESCRIPTION

补充schema

AutoSchema好用是好用,但是遇到这类问题就不能很好的解决了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class TestViewSets(viewsets.ReadOnlyModelViewSet):
"""
Return xxxxx.
"""
serializer_class = TestSerializer
permission_classes = (
permissions.IsAuthenticated,
)
def get_queryset(self):
q = self.request.query_params.get('q', None)
# some code
return queryset
@detail_route(methods=["POST"], permission_classes=[permissions.IsAuthenticated],
url_path="translation")
def translation(self, request, *args, **kwargs):
"""
xxxxx
"""
name = request.data.get('name', None)
# some code
return Response(data, status=status.HTTP_200_OK)

AutoSchema 只能加到class下,导致2个function 参数都会混在里面。
然后通过查看文档,文档里提供了这样的方法

http://www.django-rest-framework.org/api-guide/schemas/#per-view-schema-customisation

1
2
3
4
5
6
7
8
9
10
from rest_framework.views import APIView
from rest_framework.schemas import AutoSchema
class CustomSchema(AutoSchema):
def get_link(...):
# Implement custom introspection here (or in other sub-methods)
class CustomView(APIView):
...
schema = CustomSchema()

文档实在简单,不得不去看源代码去了解实现自定义的实现方法。
可以看到可以源代码中get_link的代码

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
def get_link(self, path, method, base_url):
fields = self.get_path_fields(path, method)
fields += self.get_serializer_fields(path, method)
fields += self.get_pagination_fields(path, method)
fields += self.get_filter_fields(path, method)
if self._manual_fields is not None:
by_name = {f.name: f for f in fields}
for f in self._manual_fields:
by_name[f.name] = f
fields = list(by_name.values())
if fields and any([field.location in ('form', 'body') for field in fields]):
encoding = self.get_encoding(path, method)
else:
encoding = None
description = self.get_description(path, method)
if base_url and path.startswith('/'):
path = path[1:]
return coreapi.Link(
url=urlparse.urljoin(base_url, path),
action=method.lower(),
encoding=encoding,
fields=fields,
description=description
)

既然看明白了实现过程,那么我们就来写一个custom试试。可以通过link.url来判断要给哪个路由添加什么参数。

custom_schema.py

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
class TestSchema(AutoSchema):
def get_link(self, path, method, base_url):
link = super(TestSchema, self).get_link(path, method, base_url)
get_fields = [
          coreapi.Field('q', location='query', schema=coreschema.String(description=u'filter xxxx')),
]
translation_fields = [
coreapi.Field('name', location='form', required=True,
                        schema=coreschema.String(description=u'xxxx name',)),
]
       if link.url == '/api/v1/xxxxx/' and method.lower() == 'get':
fields = tuple(get_fields) + link.fields
       elif link.url == '/api/v1/xxxxx/{id}/translation/':
fields = tuple(translation_fields) + link.fields
else:
fields = link.fields
link = coreapi.Link(
url=link.url,
action=link.action,
encoding=link.encoding,
fields=fields,
description=link.description)
coreapi.document.Link()
return link

引入custom_schema.py模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class TestViewSets(viewsets.ReadOnlyModelViewSet):
"""
Return xxxxx.
"""
serializer_class = TestSerializer
permission_classes = (
permissions.IsAuthenticated,
)
schema = TestSchema()
def get_queryset(self):
q = self.request.query_params.get('q', None)
# some code
return queryset
@detail_route(methods=["POST"], permission_classes=[permissions.IsAuthenticated],
url_path="translation")
def translation(self, request, *args, **kwargs):
"""
xxxxx
"""
name = request.data.get('name', None)
# some code
return Response(data, status=status.HTTP_200_OK)

效果图

rest doc

upload successful

swagger

upload successful

总结

前端要的schema有了,后端api文档也自动生成了,一举两得。