在使用DRF的时候,通常的文档有:默认文档RestFrameWork、CoreAPI、Swagger,Swagger是最流行的API文档库,在绝大多数服务端开发中都有用到,之前我们使用了CoreAPI来生成文档,一方面是它不够流行,没办法和其他工具结合,另一方面可能是我不熟悉,所有有些接口并不能按照我们的要求来使用。因此我选择使用Swagger文档,之前使用过drf-yasg,但是drf-yasg现在还不支持OpenAPI 3.0,而在drf-yasg的官方文档中为我们推荐了另一个库:drf-spectacular,而且声明了drf-yasg不太可能支持OpenAPI 3.0,因此推荐我们使用drf-spectacular这个库。
pipenv install drf-spectacular
在app中注册
# settings.py INSTALLED_APPS = [ # ALL YOUR APPS 'drf_spectacular', ]
配置DRF默认schema
# settings.py REST_FRAMEWORK = { # YOUR SETTINGS 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', }
配置drf-spectacular
# settings.py SPECTACULAR_SETTINGS = { 'TITLE': '在线考试', 'DESCRIPTION': '在线考试系统', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, # OTHER SETTINGS }
drf-spectacular 默认不包含UI资源,采用CDN方式引入网络外部资源,如果需要本地使用UI资源,可以按照一下方式引入:
pipenv install drf-spectacular[sidecar]
配置settings.py文件
INSTALLED_APPS = [ # ALL YOUR APPS 'drf_spectacular', 'drf_spectacular_sidecar', # required for Django collectstatic discovery ] SPECTACULAR_SETTINGS = { 'SWAGGER_UI_DIST': 'SIDECAR', # shorthand to use the sidecar instead 'SWAGGER_UI_FAVICON_HREF': 'SIDECAR', 'REDOC_DIST': 'SIDECAR', # OTHER SETTINGS }
在根urls.py中增加路由配置
from drf_spectacular.views import SpectacularJSONAPIView, SpectacularRedocView, SpectacularSwaggerView urlpatterns = [ path('swagger/json/', SpectacularJSONAPIView.as_view(), name='schema'), # Optional UI: path('swagger/ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('swagger/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), # YOUR PATTERNS ]
访问:http://localhost:8000/swagger/ui/
在swagger文档中为我们生成的接口标签是根据根路由前缀自动生成的,例如以上文档的路由为:
urlpatterns = [ path('', RedirectView.as_view(url='docs')), path('swagger/json/', SpectacularJSONAPIView.as_view(), name='schema'), # Optional UI: path('swagger/ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('swagger/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), # My Router path('user/', include('users.urls')), path('exam/', include('exam.urls')), path('question/', include('question.urls')) ]
如果想要修改指定接口所属的标签,我们可以使用drf-spectacular提供的extend_schema
装饰器函数,函数定义如下:
def extend_schema( operation_id: Optional[str] = None, parameters: Optional[List[Union[OpenApiParameter, _SerializerType]]] = None, request: Any = empty, responses: Any = empty, auth: Optional[List[str]] = None, description: Optional[str] = None, summary: Optional[str] = None, deprecated: Optional[bool] = None, tags: Optional[List[str]] = None, exclude: bool = False, operation: Optional[Dict] = None, methods: Optional[List[str]] = None, versions: Optional[List[str]] = None, examples: Optional[List[OpenApiExample]] = None, extensions: Optional[Dict[str, Any]] = None, ) -> Callable[[F], F]:
这个装饰器主要用于修改view在文档中的定义,参数意义如下:
Serializer
。需要各种各样的可单独使用或组合使用的输入(有以下7种)
Serializer
类Serializer(many=True)
OpenApiTypes
的基本类型或者实例OpenApiResponse
类PolymorphicProxySerializer
类{200, None}
)media_type
作为值Serializer
类或者实例OpenApiTypes
基本类型或者实例PolymorphicProxySerializer
类media_type
作为键,以上其中一项作为值schema
中排除操作OpenAPI3
的字典,该字典可以直接翻译成YAML
。extend_schema
中特殊的方法,默认匹配所有extend_schema
中特殊的API版本,默认匹配所有最后我们将登录、注册接口修改为Common标签
from drf_spectacular.utils import extend_schema class LoginView(GenericAPIView): ...... @extend_schema( tags=['Common'], summary='Login', description='登录接口', responses={200: str, 401: str} ) def post(self, request: Request): pass class RegisterView(GenericAPIView): ...... @extend_schema( tags=['Common'], summary='Register', description='注册接口', responses={201: UserInfoSerializer, 400: str} ) def post(self, request: Request): pass
注意:
使用时要注意,对于不同app下的view和Serializer要尽量使用不同的命名,否则在渲染文档的时候可能会出现异常。
在项目中我们使用了JWT作为登录认证,而drf-spectacular只对Session、Basic、Token做了适配
rest_framework.authentication.SessionAuthentication rest_framework.authentication.BasicAuthentication rest_framework.authentication.TokenAuthentication
这个我们在drf-spectacular/authentication.py
文件中可以看到,这个的作用就是在文档中显示什么样认证页面
对于认证页面的显示,主要是根据settings.py配置中的
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.BasicAuthentication', 'utils.auth.authentication.JwtAuthentication' ], ...... }
如果drf-spectacular
可以识别 DEFAULT_AUTHENTICATION_CLASSES
下的认证方式,就会在文档登录页面上显示对应的认证方式,这里我们有自定义的认证方式,如果需要显示,要做一下适配:
from drf_spectacular.extensions import OpenApiAuthenticationExtension from drf_spectacular.plumbing import build_bearer_security_scheme_object class JWTTokenScheme(OpenApiAuthenticationExtension): target_class = 'utils.auth.authentication.JwtAuthentication' name = 'JwtTokenAuth' match_subclasses = True priority = 1 def get_security_definition(self, auto_schema): return build_bearer_security_scheme_object( header_name='Authorization', token_prefix=self.target.keyword, bearer_format='JWT' )
简单解释一下,首先要继承OpenApiAuthenticationExtension
,然后target_class
中要写我们在DEFAULT_AUTHENTICATION_CLASSES
中配置的认证路径,然后重新get_security_definition
函数,返回一个字典对象,字典的键可以在OpenAPI Specification v3.0.3 | Introduction, Definitions, & More网页访问
然后再看登录认证页面
因为我们在DEFAULT_AUTHENTICATION_CLASSES
中配置了两种认证方式,因此页面就会显示两种认证方式
目前使用中存在一个BUG,就是对于read_only字段,按照我们的理解就是在查询请求是返回给客户端,而创建时在请求体中不需要包含。在默认生成的swagger界面上,我们看到的情况与理解的一样,对于JSON参数的请求是没有问题的,我们只需要输入必填的字段就可以了,但是如果是form-data参数,虽然显示的依然不包含read_only字段,请求却无法发送成功。作者也认为这是一个BUG,但是他却没有修正,
Callback schema with read-only/write-only fields · Issue #680 · tfranzel/drf-spectacular (github.com)
对于以上问题我们有两种解决方式:
目前我采用的是第一种方式,宁愿API不明确一点,也不能增加后端的复制程度。