← 编程返回顶部

drf-spectacular

drf-spectacular 是 Django REST Framework(DRF)的 OpenAPI Schema 生成工具,支持 OpenAPI 3.0 和 3.1。

其主要作用是根据 DRF 的 Serializer、View、URL、Authentication 等信息自动生成 OpenAPI 文档,并允许开发者通过装饰器和扩展机制补充无法自动推断的内容。

典型工作流程:

Django Model / Serializer / View
                ↓
          drf-spectacular
                ↓
          OpenAPI Schema
                ↓
       ┌────────┴────────┐
       ↓                 ↓
   Swagger UI           Orval
                         ↓
                TypeScript Types
                Axios / Query Hooks

1. 基础配置与自动推断

基础配置

# settings.py

INSTALLED_APPS = [
    "rest_framework",
    "drf_spectacular",
]

REST_FRAMEWORK = {
    "DEFAULT_SCHEMA_CLASS":
        "drf_spectacular.openapi.AutoSchema",
}

SPECTACULAR_SETTINGS = {
    "TITLE": "Example API",
    "DESCRIPTION": "Example API Documentation",
    "VERSION": "1.0.0",
}

自动生成 Schema

最基础的情况通常不需要额外编写 drf-spectacular 代码。

from rest_framework import serializers
from rest_framework.generics import RetrieveAPIView


class UserProfileSerializer(serializers.Serializer):
    name = serializers.CharField()
    avatar = serializers.URLField(allow_null=True)


class UserProfileView(RetrieveAPIView):
    serializer_class = UserProfileSerializer
    queryset = User.objects.all()

drf-spectacular 可以根据 serializer_class 自动推断响应结构:

responses:
  "200":
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/UserProfile"

自动推断主要依赖:

  • serializer_class / get_serializer_class()
  • queryset
  • get_serializer()
  • View 类型及对应的 HTTP 操作
  • DRF 的认证、分页和过滤器配置

GenericAPIView、RetrieveAPIView、ListAPIView、ModelViewSet 通常比裸 APIView 更容易自动推断。

注意:仅仅在 APIView.get() 内部调用某个 Serializer,并不意味着 drf-spectacular 能自动识别该 Serializer。

2. @extend_schema

@extend_schema 是 drf-spectacular 最常用的装饰器,用于补充或覆盖自动推断的信息。

from drf_spectacular.utils import extend_schema


@extend_schema(
    request=UsernameUpdateSerializer,
    responses={200: UserProfileSerializer},
)
def patch(self, request):
    ...

对应:

PATCH Request Body
        ↓
UsernameUpdateSerializer

HTTP 200 Response
        ↓
UserProfileSerializer

常用参数

参数作用
request请求体 Schema
responses响应 Schema
parametersQuery、Path、Header、Cookie 参数
operation_idOpenAPI 操作唯一标识
summary接口简短描述
description接口详细描述
tags接口分组
examples请求或响应示例
auth覆盖自动推断的认证要求
deprecated标记接口已弃用
exclude从 Schema 中排除接口
methods限定装饰器适用的 HTTP 方法
versions限定适用的 API 版本

原则:优先使用自动推断,仅对推断错误或无法推断的内容使用 @extend_schema。

3. GET Parameters 与 POST Request Body

GET 请求通常使用 Query Parameters:

@extend_schema(
    parameters=[SearchPoiSerializer],
    responses=SearchPoiResponseSerializer,
)
def get(self, request):
    ...

对应:

GET /api/v1/pois?name=Tokyo&limit=10

POST 请求通常使用 Request Body:

@extend_schema(
    request=SearchPoiSerializer,
    responses=SearchPoiResponseSerializer,
)
def post(self, request):
    ...

对应:

POST /api/v1/pois/search
Content-Type: application/json

{
  "name": "Tokyo",
  "limit": 10
}

区别

参数OpenAPI 对象主要用途
parametersParameter ObjectQuery、Path、Header、Cookie
requestRequest Body ObjectJSON、Form、Multipart 等请求体
responsesResponses ObjectHTTP 响应

注意:

  • POST 也可以有 Query Parameters。
  • GET 通常不使用 Request Body。
  • parameters 既可以传入 OpenApiParameter,也可以传入用于描述 Query 参数的 Serializer。
  • request 通常传入 Serializer、基本类型或原始 Schema 定义。

4. OpenApiParameter

用于显式描述请求参数。

from drf_spectacular.utils import (
    OpenApiParameter,
    OpenApiTypes,
)

Query 参数

@extend_schema(
    parameters=[
        OpenApiParameter(
            name="keyword",
            type=OpenApiTypes.STR,
            location=OpenApiParameter.QUERY,
            required=True,
            description="Search keyword",
        ),
        OpenApiParameter(
            name="limit",
            type=OpenApiTypes.INT,
            location=OpenApiParameter.QUERY,
            required=False,
            default=20,
        ),
    ]
)
def get(self, request):
    ...

对应:

GET /api/v1/search?keyword=tokyo&limit=20

Path 参数

OpenApiParameter(
    name="poi_id",
    type=OpenApiTypes.INT,
    location=OpenApiParameter.PATH,
    required=True,
)

对应:

GET /api/v1/pois/123

Path 参数必须是必填项。

Header 参数

OpenApiParameter(
    name="X-Request-ID",
    type=OpenApiTypes.STR,
    location=OpenApiParameter.HEADER,
)

对应请求:

X-Request-ID: abc-123

响应 Header

OpenApiParameter 也支持通过 response 参数描述响应 Header。

OpenApiParameter(
    name="Retry-After",
    type=OpenApiTypes.INT,
    location=OpenApiParameter.HEADER,
    response=[429],
    description="Retry delay in seconds",
)

然后传入 @extend_schema(parameters=[...])。

它描述的是:

HTTP/1.1 429 Too Many Requests
Retry-After: 35

而不是客户端发送的请求 Header。

5. Responses 与 OpenApiResponse

根据状态码定义响应

@extend_schema(
    responses={
        200: UserProfileSerializer,
        400: ValidationErrorSerializer,
        401: AuthenticationErrorSerializer,
        429: RateLimitErrorSerializer,
    }
)
def get(self, request):
    ...

会生成不同 HTTP 状态码对应的响应结构。

OpenApiResponse

OpenApiResponse 可以进一步补充响应描述和示例。

from drf_spectacular.utils import OpenApiResponse


@extend_schema(
    responses={
        200: OpenApiResponse(
            response=UserProfileSerializer,
            description="User profile retrieved",
        ),
        404: OpenApiResponse(
            response=ErrorSerializer,
            description="User not found",
        ),
    }
)
def get(self, request):
    ...

对于没有响应体的操作:

@extend_schema(
    responses={
        204: OpenApiResponse(
            response=None,
            description="Successfully deleted",
        )
    }
)
def delete(self, request):
    ...

多种媒体类型

@extend_schema(
    responses={
        (200, "application/json"): UserSerializer,
        (200, "application/pdf"): OpenApiTypes.BINARY,
    }
)
def get(self, request):
    ...

不同媒体类型可以分别指定 Schema。

实践上不必为每个接口机械地重复所有错误状态码,但应保证文档包含客户端需要处理的重要错误响应。通用错误可以通过公共工具或项目级机制统一维护。

6. @extend_schema_view

@extend_schema_view 用于在类级别集中描述多个方法。

from drf_spectacular.utils import (
    extend_schema,
    extend_schema_view,
)


@extend_schema_view(
    get=extend_schema(
        responses=UserProfileSerializer,
    ),
    patch=extend_schema(
        request=UserProfileUpdateSerializer,
        responses=UserProfileSerializer,
    ),
    delete=extend_schema(
        request=None,
        responses={204: None},
    ),
)
class UserProfileView(GenericAPIView):
    ...

作用相当于分别为 get()、patch()、delete() 添加 @extend_schema。

对于 ViewSet,需要使用 Action 名称:

@extend_schema_view(
    list=extend_schema(tags=["Users"]),
    retrieve=extend_schema(tags=["Users"]),
    create=extend_schema(tags=["Users"]),
)
class UserViewSet(ModelViewSet):
    ...

特别适合继承 DRF Mixin、没有显式覆盖操作方法的场景。

7. @extend_schema_field

对于自定义 Serializer Field,drf-spectacular 可能无法准确推断其返回类型。

例如:

class CoordinatesField(serializers.Field):
    def to_representation(self, value):
        return [value.longitude, value.latitude]

可以使用:

from drf_spectacular.utils import extend_schema_field


@extend_schema_field({
    "type": "array",
    "items": {"type": "number"},
    "minItems": 2,
    "maxItems": 2,
})
class CoordinatesField(serializers.Field):
    ...

对应:

type: array
items:
  type: number
minItems: 2
maxItems: 2

这比简单声明 ListField(FloatField()) 更明确,因为它限定了坐标数组长度。

注意:上述结构在 OpenAPI 中表示长度为 2 的数值数组,但 TypeScript 代码生成器不一定将其识别为固定长度元组 [number, number],也可能生成 number[]。

8. SerializerMethodField

对于 SerializerMethodField,建议使用类型注解或 @extend_schema_field 明确返回类型。

class UserSerializer(serializers.Serializer):
    display_name = serializers.SerializerMethodField()

    @extend_schema_field(serializers.CharField())
    def get_display_name(self, obj):
        return obj.name.upper()

也可以使用 Python 类型:

@extend_schema_field(str)
def get_display_name(self, obj):
    return obj.name.upper()

简单类型情况下,明确的返回类型注解也可能足够:

def get_display_name(self, obj) -> str:
    return obj.name.upper()

如果返回复杂对象、列表或嵌套结构,更推荐显式使用 Serializer 描述。

9. @extend_schema_serializer

这是原笔记中值得补充的常用装饰器。

@extend_schema_serializer 用于调整整个 Serializer 的 Schema 表现。

例如:

from drf_spectacular.utils import extend_schema_serializer


@extend_schema_serializer(
    exclude_fields=("internal_note",),
)
class UserSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    username = serializers.CharField()
    internal_note = serializers.CharField()

生成 Schema 时忽略 internal_note。

注意:它只影响 Schema 文档,不会自动改变 DRF Serializer 实际输出的数据。

Envelope 结构

某些 API 采用统一响应包装:

{
  "success": true,
  "data": {
    "id": 1,
    "username": "Alice"
  }
}

可以显式定义响应 Serializer:

class UserResponseSerializer(serializers.Serializer):
    success = serializers.BooleanField()
    data = UserSerializer()

如果有大量统一包装结构,可以进一步使用 extend_schema_serializer、动态 Serializer 工厂或项目级扩展来复用 Schema 定义。

10. inline_serializer

对于结构简单、只使用一次的响应,不一定需要单独定义 Serializer 类。

from drf_spectacular.utils import inline_serializer


@extend_schema(
    responses=inline_serializer(
        name="HealthResponse",
        fields={
            "status": serializers.CharField(),
            "version": serializers.CharField(),
        },
    )
)
def get(self, request):
    ...

对应:

{
  "status": "ok",
  "version": "1.0.0"
}

适用场景:

  • Health Check。
  • 简单的状态响应。
  • 一次性的小型数据结构。

如果同一结构在多个接口中重复出现,仍然建议定义独立 Serializer,便于复用和维护。

11. OpenApiExample

OpenApiExample 用于为请求或响应添加示例。

from drf_spectacular.utils import OpenApiExample


@extend_schema(
    request=UsernameUpdateSerializer,
    examples=[
        OpenApiExample(
            name="Username Update",
            value={
                "username": "alice"
            },
            request_only=True,
        ),
    ],
)
def patch(self, request):
    ...

常用参数:

参数作用
name示例名称
value示例数据
summary简短说明
description详细描述
request_only仅用于请求
response_only仅用于响应
media_type限定媒体类型
status_codes限定响应状态码

例如为 200 响应添加示例:

OpenApiExample(
    name="Success",
    value={
        "id": 1,
        "username": "alice",
    },
    response_only=True,
    status_codes=["200"],
)

示例不会自动验证运行时返回的数据,只用于接口文档与相关工具展示。

12. 文件上传与 OpenApiRequest

对于文件上传接口,需要描述 multipart/form-data。

Serializer

class AvatarUploadSerializer(serializers.Serializer):
    avatar = serializers.ImageField()

View

from rest_framework.parsers import MultiPartParser
from drf_spectacular.utils import OpenApiRequest


class AvatarUploadView(GenericAPIView):
    parser_classes = [MultiPartParser]

    @extend_schema(
        request={
            "multipart/form-data": OpenApiRequest(
                request=AvatarUploadSerializer,
            )
        },
        responses={200: AvatarResponseSerializer},
    )
    def post(self, request):
        ...

OpenAPI 文件字段通常对应:

type: string
format: binary

需要注意:

  • parser_classes 决定 DRF 实际支持的请求数据解析方式。
  • @extend_schema 负责描述接口契约。
  • Schema 定义不会自动赋予 View 文件上传处理能力。

对于请求和响应中字段表示不同的情况,可考虑:

SPECTACULAR_SETTINGS = {
    "COMPONENT_SPLIT_REQUEST": True,
}

这有助于更准确地描述 FileField、只读字段、必填字段等在请求与响应中的差异,但可能增加生成的 Schema 组件数量。

13. Authentication Extension

对于自定义认证机制,drf-spectacular 可能无法自动识别。

可以使用:

from drf_spectacular.extensions import (
    OpenApiAuthenticationExtension,
)

例如:

class BrowserSessionAuthenticationScheme(
    OpenApiAuthenticationExtension
):
    target_class = (
        "myapp.authentication.BrowserSessionAuthentication"
    )
    name = "browserSession"

    def get_security_definition(self, auto_schema):
        return {
            "type": "apiKey",
            "in": "cookie",
            "name": "sorivel_session",
        }

对应 OpenAPI:

components:
  securitySchemes:
    browserSession:
      type: apiKey
      in: cookie
      name: sorivel_session

这里定义的是认证凭据如何传递。

Extension 不会自动实现认证逻辑,也不会自动实现 CSRF 防护。

实际认证逻辑仍然由 DRF Authentication Class 负责。

自定义认证一般适合使用 Extension 在项目级统一描述,不需要每个 View 重复声明。

14. 其他 Extension

drf-spectacular 提供多种 Extension,用于修改自动推断行为。

Extension作用
OpenApiAuthenticationExtension自定义认证机制
OpenApiSerializerFieldExtension自定义或第三方 Serializer Field
OpenApiSerializerExtension修改 Serializer 推断
OpenApiViewExtension修改无法直接控制的 View 推断
OpenApiFilterExtension自定义过滤器或分页参数推断

Extension 通常用于:

  • 第三方库。
  • 公共基础类。
  • 无法直接添加装饰器的代码。
  • 项目中需要统一处理的自定义组件。

Extension 注册

Extension 类需要在 Schema 生成之前被 Python 导入。

可以放在:

myapp/
├── apps.py
├── schema.py
├── serializers.py
└── views.py

然后在 apps.py 中导入:

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        import myapp.schema  # noqa: F401

这样 drf-spectacular 生成 Schema 时即可发现对应 Extension。

15. Postprocessing Hook

Postprocessing Hook 用于在 OpenAPI Schema 完整生成后,对结果进行统一修改。

def postprocess_contract(
    result,
    generator,
    request,
    public,
):
    # 修改 OpenAPI Schema
    return result

配置:

SPECTACULAR_SETTINGS = {
    "POSTPROCESSING_HOOKS": [
        "drf_spectacular.hooks.postprocess_schema_enums",
        "common.api.schema.postprocess_contract",
    ],
}

注意:重新设置 POSTPROCESSING_HOOKS 会覆盖默认配置。

如果需要保留默认 Enum 处理,应明确添加:

"drf_spectacular.hooks.postprocess_schema_enums"

典型用途

  • 修改生成的 operationId。
  • 为部分 Schema 补充统一元数据。
  • 调整组件命名或引用。
  • 修改通用响应结构。
  • 处理代码生成器无法兼容的特殊 Schema。

Preprocessing Hook

与 Postprocessing 不同,Preprocessing Hook 在 Schema 生成之前执行,主要用于处理待生成的接口列表。

例如:

SPECTACULAR_SETTINGS = {
    "PREPROCESSING_HOOKS": [
        "drf_spectacular.hooks.preprocess_exclude_path_format",
    ],
}

该内置 Hook 可以删除由 DRF format suffix 产生的重复路径。

原则:Serializer 能表达的优先使用 Serializer;extend_schema 能表达的优先使用 extend_schema;Hook 主要处理全局性的 Schema 转换。

16. 自定义 AutoSchema

drf-spectacular 默认使用:

drf_spectacular.openapi.AutoSchema

可以通过继承改变底层推断行为:

from drf_spectacular.openapi import AutoSchema


class ApiAutoSchema(AutoSchema):
    ...

常见用途包括:

  • 定制 Operation ID 生成。
  • 修改参数推断。
  • 修改响应结构推断。
  • 修改认证要求的推断。

例如覆盖 Operation ID:

class ApiAutoSchema(AutoSchema):
    def get_operation_id(self):
        operation_id = super().get_operation_id()
        return operation_id

实际项目中应尽量避免随意覆盖内部推断方法,因为此类定制与 drf-spectacular 的实现细节耦合较强,升级版本时需要重新验证兼容性。

17. operationId

每个 OpenAPI Operation 应具有唯一的 operationId。

例如:

operationId: getUserProfile

Orval 等代码生成工具通常根据它生成 API 函数或 Hook:

getUserProfile();
useGetUserProfile();

具体名称取决于代码生成配置。

手动指定

@extend_schema(
    operation_id="getUserProfile",
)
def get(self, request):
    ...

自动生成

drf-spectacular 可以根据 URL、HTTP 方法等信息自动生成 Operation ID。

例如:

/api/v1/users/profile/
      ↓
users_profile_retrieve

是否需要手动指定,取决于项目需求:

  • 内部 API、命名规则统一:优先自动生成。
  • 已发布的前端 SDK:建议保持 Operation ID 长期稳定。
  • 自动生成名称不清晰或发生冲突:可以手动指定。

对于 Orval,operationId 变化可能导致生成的函数名变化,进而影响前端代码。

18. Tags

Tags 用于接口分组:

@extend_schema(
    tags=["Users"],
)
def get(self, request):
    ...

Swagger UI 和 ReDoc 通常根据 Tags 显示接口分组。

Orval 的:

mode: "tags-split";

也可以根据 Tags 拆分生成的 API 文件。

因此,Tags 不仅影响文档展示,也可能影响前端生成代码的目录结构。

建议一个业务模块采用统一的 Tag 命名,例如:

Users
POI
Itinerary
Authentication

19. 常用 SPECTACULAR_SETTINGS

配置作用
TITLEAPI 标题
DESCRIPTIONAPI 描述
VERSIONAPI 版本
SERVERSAPI 服务器地址
TAGS顶层 Tag 定义
COMPONENT_SPLIT_REQUEST分离请求和响应 Schema
COMPONENT_NO_READ_ONLY_REQUIRED调整只读字段的 required 表现
ENUM_NAME_OVERRIDES自定义枚举组件名称
POSTPROCESSING_HOOKSSchema 生成后的处理
PREPROCESSING_HOOKSSchema 生成前的处理
SORT_OPERATIONS接口排序
SCHEMA_PATH_PREFIXSchema 路径前缀识别
OAS_VERSIONOpenAPI 规范版本

大多数项目不需要修改所有配置。

推荐先保持默认配置,只针对实际问题进行调整。

20. Schema 生成与 CI 校验

生成 Schema

python manage.py spectacular \
    --file openapi.yaml \
    --validate \
    --fail-on-warn

参数:

  • --file:输出文件。
  • --validate:校验生成的 OpenAPI Schema。
  • --fail-on-warn:出现 Schema 生成警告时让命令失败。

需要区分:

  • Schema 校验:验证文档结构是否符合 OpenAPI 规范。
  • API 测试:验证真实接口是否按预期工作。
  • TypeScript Typecheck:验证前端调用代码的类型是否正确。

这三者不能互相替代。

CI/CD 流程

修改 Django API
        ↓
      pytest
        ↓
spectacular --validate
        ↓
   生成 openapi.yaml
        ↓
       Orval
        ↓
TypeScript Typecheck
        ↓
   Build / Deploy

通过 CI 可以尽早发现:

  • Schema 无法正确生成。
  • Serializer 字段变化。
  • Operation ID 发生变化。
  • 生成的 TypeScript 类型与现有代码不兼容。
  • API Client 生成失败。

但如果前后端位于不同仓库,需要明确 Schema 如何同步以及在哪个仓库执行 Orval。

21. 常见问题与实践原则

为什么 Schema 会生成错误类型?

常见原因:

  • 自定义 Serializer Field 无法自动推断。
  • SerializerMethodField 缺少类型信息。
  • View 没有声明 Serializer。
  • 实际 Response 结构与 Serializer 不一致。
  • 自定义认证或分页机制无法识别。

应优先在对应的 Serializer、View 或 Field 上补充准确的信息,而不是直接修改生成的 YAML。

是否需要为每个接口编写 extend_schema?

不需要。

如果 DRF 自身的定义已经足够明确,drf-spectacular 就能自动生成大量 Schema 信息。

推荐的扩展优先级

DRF 自然定义
       ↓
Serializer / GenericAPIView
       ↓
@extend_schema
       ↓
@extend_schema_field
@extend_schema_serializer
       ↓
Extension
       ↓
Pre/Postprocessing Hook
       ↓
自定义 AutoSchema

这是一种工程上的选择顺序,而不是框架强制规定。

越靠后,通常意味着更强的定制能力,同时也可能增加维护成本。

总结

drf-spectacular 的重要功能可以分为四类:

分类核心内容
自动生成Serializer、View、queryset、AutoSchema
接口定制extend_schema、extend_schema_view、OpenApiParameter、OpenApiResponse
Schema 定制extend_schema_field、extend_schema_serializer、inline_serializer、OpenApiExample
高级扩展Extensions、Hooks、自定义 AutoSchema

对于普通 Django REST Framework 项目,最需要熟悉:

  1. serializer_class 与自动推断。
  2. @extend_schema 和 @extend_schema_view。
  3. parameters、request、responses。
  4. @extend_schema_field。
  5. operation_id 和 Tags。
  6. Schema 校验与客户端代码生成。

当项目包含自定义认证、复杂响应包装、第三方 Serializer Field 或统一 Schema 转换需求时,再考虑 Extension、Hook 和自定义 AutoSchema。


参考文档