URLPathVersioning 是 DRF 将版本号嵌入 URL 路径(如 /api/v1/users/)的版本控制方案,适合对外公开、需 CDN/网关识别的 RESTful 接口;但要求路由显式分版本、不支持动态路径或同一路径多版本切换。

URLPathVersioning 是什么,它适合什么场景
URLPathVersioning 是 Django REST Framework(DRF)内置的一种版本控制方案,它把版本号直接写在 URL 路径里,比如 /api/v1/users/ 或 /api/v2/orders/。这种方式对前端最友好,也最容易被 API 文档、网关、CDN 缓存识别,适合对外公开的 RESTful 接口。
但它不是万能的:
- 不适用于需要同一资源路径兼容多版本的场景(比如用请求头切换版本)
- 一旦发布 v1,后续所有新接口默认都要带
v1前缀,否则就“无版本”——DRF 默认会拒绝无版本的请求(除非你显式配置default_version=None) - 版本路径是硬编码进 URLconf 的,不能靠参数动态生成多个版本路由
怎么在 Django 中启用 URLPathVersioning
核心是两步:配置 DRF 全局设置 + 修改 URL 路由结构。
-
在
settings.py中添加或修改REST_FRAMEWORK配置:立即学习“Python免费学习笔记(深入)”;
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning', 'DEFAULT_VERSION': 'v1', 'ALLOWED_VERSIONS': ['v1', 'v2'], 'VERSION_PARAM': 'version', # 这个参数名其实不影响 URLPathVersioning,仅作占位 } -
在主
urls.py中,把版本前缀作为一级路径分组:from django.urls import path, include </li></ul><p>urlpatterns = [ path('api/v1/', include('myapp.urls_v1')), path('api/v2/', include('myapp.urls_v2')), ]</p>注意:DRF 的
URLPathVersioning不会自动解析路径中的v1—— 它依赖你把版本段显式写进path(),然后通过request.version拿到值。所以必须手动拆分路由模块,不能只靠一个include('myapp.urls')。为什么 request.version 总是 None 或报错
这是最常踩的坑:你以为配了
URLPathVersioning就能自动提取版本,结果request.version是None,或者视图里调用self.request.version报错。根本原因只有两个:
- 路由没按版本分组,比如写了
path('api/', include(...)),但没把v1或v2写进路径 - 视图没继承
APIView或没加@api_view装饰器(函数视图),导致 DRF 的版本解析中间件没生效
验证方法:在视图里打印
request.versioning_scheme,如果不是URLPathVersioning实例,说明版本控制根本没加载成功。另外注意:如果用了
DefaultRouter,它生成的 URL 不含版本前缀,必须手动包装,例如:from rest_framework.routers import DefaultRouter <p>router_v1 = DefaultRouter() router_v1.register(r'users', UserViewSetV1)</p><p>urlpatterns += [ path('api/v1/', include(router_v1.urls)), ]</p>不同版本共用模型但逻辑不同,怎么组织代码
别把所有版本逻辑塞进一个
views.py里用 if 判断request.version。容易失控,测试难,上线回滚风险高。推荐做法是:
- 每个版本一个独立模块:
views_v1.py、views_v2.py - 视图类名带上版本标识:
UserViewSetV1、UserViewSetV2 - 序列化器也对应拆分:
serializers_v1.py,避免fields或to_representation里堆条件判断 - 如果只是字段增减,可考虑用
Serializer的<strong>init</strong>根据context.get('request')动态调整fields,但仅限简单场景;复杂行为仍建议分文件
还有一个隐性成本:迁移数据库时,
v1接口可能还在线上跑着,但v2已经要求新字段非空。这时候得在v1的序列化器里保留兼容逻辑,比如给新字段设required=False、提供默认值,而不是指望数据库迁移脚本一劳永逸。版本控制真正难的不是配置,而是让旧版不崩、新版可测、两边数据语义一致。路径里写个
v1很快,但想清楚每个字段在 v1/v2 下的生命周期,才决定要不要拆模块、要不要加 migration 条件分支。 - 路由没按版本分组,比如写了


















