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()querysetget_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 |
parameters | Query、Path、Header、Cookie 参数 |
operation_id | OpenAPI 操作唯一标识 |
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 对象 | 主要用途 |
|---|---|---|
parameters | Parameter Object | Query、Path、Header、Cookie |
request | Request Body Object | JSON、Form、Multipart 等请求体 |
responses | Responses Object | HTTP 响应 |
注意:
- 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
| 配置 | 作用 |
|---|---|
TITLE | API 标题 |
DESCRIPTION | API 描述 |
VERSION | API 版本 |
SERVERS | API 服务器地址 |
TAGS | 顶层 Tag 定义 |
COMPONENT_SPLIT_REQUEST | 分离请求和响应 Schema |
COMPONENT_NO_READ_ONLY_REQUIRED | 调整只读字段的 required 表现 |
ENUM_NAME_OVERRIDES | 自定义枚举组件名称 |
POSTPROCESSING_HOOKS | Schema 生成后的处理 |
PREPROCESSING_HOOKS | Schema 生成前的处理 |
SORT_OPERATIONS | 接口排序 |
SCHEMA_PATH_PREFIX | Schema 路径前缀识别 |
OAS_VERSION | OpenAPI 规范版本 |
大多数项目不需要修改所有配置。
推荐先保持默认配置,只针对实际问题进行调整。
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 项目,最需要熟悉:
serializer_class与自动推断。@extend_schema和@extend_schema_view。parameters、request、responses。@extend_schema_field。operation_id和 Tags。- Schema 校验与客户端代码生成。
当项目包含自定义认证、复杂响应包装、第三方 Serializer Field 或统一 Schema 转换需求时,再考虑 Extension、Hook 和自定义 AutoSchema。