• Django Swagger文档库drf-spectacular


    在使用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
    
    • 1

    在app中注册

    # settings.py
    INSTALLED_APPS = [
        # ALL YOUR APPS
        'drf_spectacular',
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置DRF默认schema

    # settings.py
    REST_FRAMEWORK = {
        # YOUR SETTINGS
        'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置drf-spectacular

    # settings.py
    SPECTACULAR_SETTINGS = {
        'TITLE': '在线考试',
        'DESCRIPTION': '在线考试系统',
        'VERSION': '1.0.0',
        'SERVE_INCLUDE_SCHEMA': False,
        # OTHER SETTINGS
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    静态资源引入

    drf-spectacular 默认不包含UI资源,采用CDN方式引入网络外部资源,如果需要本地使用UI资源,可以按照一下方式引入:

    pipenv install drf-spectacular[sidecar]
    
    • 1

    配置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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    路由配置

    在根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
    ]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    访问: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'))
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果想要修改指定接口所属的标签,我们可以使用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]:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这个装饰器主要用于修改view在文档中的定义,参数意义如下:

    • operation_id:一个唯一标识ID,基本用不到
    • parameters:添加到列表中的附加或替换参数去自动发现字段。
    • responses:替换Serializer。需要各种各样的可单独使用或组合使用的输入(有以下7种)
      • Serializer
      • 序列化实例,比如:Serializer(many=True)
      • OpenApiTypes的基本类型或者实例
      • OpenApiResponse
      • PolymorphicProxySerializer
      • 1个字典,以状态码作为键, 以上其中一项作为值(是最常用的,格式{200, None})
      • 1个字典,以状态码作为键,以media_type作为值
    • request:替换序列化,接受各种输入
      • Serializer 类或者实例
      • OpenApiTypes基本类型或者实例
      • PolymorphicProxySerializer
      • 1个字典,以media_type作为键,以上其中一项作为值
    • auth:用auth方法的显式列表替换发现的auth
    • description:替换发现的文档字符串
    • summary:一个可选的短的总结描述
    • deprecated:将操作标记为已弃用
    • tags:覆盖默认标记列表
    • exclude:设置为True以从schema中排除操作
    • operation:手动覆盖自动发现将生成的内容。你必须提供一个兼容OpenAPI3的字典,该字典可以直接翻译成YAML
    • methods:检查extend_schema中特殊的方法,默认匹配所有
    • versions:检查extend_schema中特殊的API版本,默认匹配所有
    • example:将请求/响应示例附加到操作中
    • extensions:规范扩展

    最后我们将登录、注册接口修改为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
    
    
    • 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

    注意:

    使用时要注意,对于不同app下的view和Serializer要尽量使用不同的命名,否则在渲染文档的时候可能会出现异常。

    自定义认证方式

    在项目中我们使用了JWT作为登录认证,而drf-spectacular只对Session、Basic、Token做了适配

    rest_framework.authentication.SessionAuthentication
    rest_framework.authentication.BasicAuthentication
    rest_framework.authentication.TokenAuthentication
    
    
    • 1
    • 2
    • 3
    • 4

    这个我们在drf-spectacular/authentication.py文件中可以看到,这个的作用就是在文档中显示什么样认证页面

    对于认证页面的显示,主要是根据settings.py配置中的

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework.authentication.BasicAuthentication',
            'utils.auth.authentication.JwtAuthentication'
        ],
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果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'
            )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    简单解释一下,首先要继承OpenApiAuthenticationExtension,然后target_class中要写我们在DEFAULT_AUTHENTICATION_CLASSES中配置的认证路径,然后重新get_security_definition函数,返回一个字典对象,字典的键可以在OpenAPI Specification v3.0.3 | Introduction, Definitions, & More网页访问

    然后再看登录认证页面

    因为我们在DEFAULT_AUTHENTICATION_CLASSES中配置了两种认证方式,因此页面就会显示两种认证方式

    BUG

    目前使用中存在一个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)

    对于以上问题我们有两种解决方式:

    1. 只使用JSON格式的请求参数,缺点是必填和选填参数搞不清楚
    2. 在后端序列化的时候,针对不同的请求,明确的定义相对应的序列化类来处理,缺点是后端代码变多了,而且埋没了DRF为我们提供的很多使用方便的特性。

    目前我采用的是第一种方式,宁愿API不明确一点,也不能增加后端的复制程度。

    关注“Python运维中心”,了解更多低代码开发

  • 相关阅读:
    【tomcat】java.lang.Exception: Socket bind failed: [730048
    STL标准模板库
    总结文件系统相关知识
    LeetCode 865. Smallest Subtree with all the Deepest Nodes【树,DFS,BFS,哈希表】1534
    24 Java xml&&枚举&&注解 详解~
    Linux开发板(树莓派)和服务器进行双向通信(socket)
    【随想】每日两题Day.4
    MQTT协议消息代理服务远程连接
    【精讲】vue框架 vue脚手架多种方法安装(教程)及注意事项、系统安装时操作代码
    大家都能看得懂的源码 - 那些关于DOM的常见Hook封装(二)
  • 原文地址:https://blog.csdn.net/wuqing942274053/article/details/126779458