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

在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在文档中的定义,参数意义如下:

  • 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

注意:

使用时要注意,对于不同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

目前使用中存在一个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运维中心”,了解更多低代码开发
公众号

本文作者:星星在线

本文链接:https://www.cnblogs.com/small-bud/p/16672213.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   星星在线  阅读(231)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑
  1. 1 Victory REOL
Victory - REOL
00:00 / 00:00
An audio error has occurred.

作曲 : Reol

作词 : Reol

fade away...do over again...

fade away...do over again...

歌い始めの一文字目 いつも迷ってる

歌い始めの一文字目 いつも迷ってる

どうせとりとめのないことだけど

伝わらなきゃもっと意味がない

どうしたってこんなに複雑なのに

どうしたってこんなに複雑なのに

噛み砕いてやらなきゃ伝わらない

ほら結局歌詞なんかどうだっていい

僕の音楽なんかこの世になくたっていいんだよ

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.

目の前 広がる現実世界がまた歪んだ

目の前 広がる現実世界がまた歪んだ

何度リセットしても

僕は僕以外の誰かには生まれ変われない

「そんなの知ってるよ」

気になるあの子の噂話も

シニカル標的は次の速報

麻痺しちゃってるこっからエスケープ

麻痺しちゃってるこっからエスケープ

遠く遠くまで行けるよ

安定なんてない 不安定な世界

安定なんてない 不安定な世界

安定なんてない きっと明日には忘れるよ

fade away...do over again...

fade away...do over again...

そうだ世界はどこかがいつも嘘くさい

そうだ世界はどこかがいつも嘘くさい

綺麗事だけじゃ大事な人たちすら守れない

くだらない 僕らみんなどこか狂ってるみたい

本当のことなんか全部神様も知らない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.