Django REST Framework 路由指南:SimpleRouter、DefaultRouter 与自定义路由器的完整实战解析

发布时间:2026/9/19 20:10:48
Django REST Framework 路由指南:SimpleRouter、DefaultRouter 与自定义路由器的完整实战解析 Django REST Framework 路由指南SimpleRouter、DefaultRouter 与自定义路由器的完整实战解析【免费下载链接】django-rest-frameworkWeb APIs for Django. 项目地址: https://gitcode.com/gh_mirrors/dj/django-rest-framework本指南系统讲解 Django REST FrameworkDRF的路由机制如何用一行register()把 ViewSet 映射为一组标准的 RESTful URL如何控制 URL 命名、尾斜杠、lookup 正则与 path 转换器如何为action自定义路由以及如何通过Route/DynamicRoute命名元组编写完全符合业务需要的自定义路由器。读完本文你将掌握从最小可用路由到深度定制路由的全套技能并理解 DRF 路由在源码层面的实现原理。在 DRF 中Routers路由器负责自动完成 URL 与视图逻辑之间的映射你只需要把 ViewSet 注册到路由器上剩下的 list / create / retrieve / update / partial_update / destroy 以及action装饰的额外动作都会被自动生成对应的 URL 模式与 URL 名称。这套机制源自 Ruby on Rails 的资源路由resourceful routing思想——不再为每个视图手写独立的 URL 配置而是用一行代码声明整组路由。一、快速上手用 SimpleRouter 生成基础路由使用路由器最简单的方式就是实例化SimpleRouter调用register()注册 ViewSet然后把router.urls交给 Django 的urlpatterns# urls.py from rest_framework import routers router routers.SimpleRouter() router.register(rusers, UserViewSet) router.register(raccounts, AccountViewSet) urlpatterns router.urlsregister()方法有两个必填参数参数说明prefix这一组路由使用的 URL 前缀例如rusersviewsetViewSet 类例如UserViewSet以及一个可选参数参数说明basename生成 URL 名称时使用的基底。若不指定会基于 ViewSet 的queryset属性自动推断取queryset.model._meta.object_name.lower()。注意如果 ViewSet 没有queryset属性则注册时必须显式设置basename上面的示例会生成如下 URL 模式URL 模式URL 名称^users/$user-list^users/{pk}/$user-detail^accounts/$account-list^accounts/{pk}/$account-detail1.1 关于 basename 的自动推断与常见报错basename用来指定视图名称模式的前半部分上例中的user或account。通常情况下你不需要显式指定它但当你自定义了get_queryset方法、导致 ViewSet 没有.queryset类属性时注册就会失败报错如下basename argument not specified, and could not automatically determine the name from the viewset, as it does not have a .queryset attribute.这说明必须显式传入basename。从源码看这一逻辑位于 routers.py 中的get_default_basename它读取 ViewSet 的queryset类属性取queryset.model._meta.object_name.lower()作为默认 basename拿不到queryset时直接assert抛出上述错误。另外BaseRouter.register还会检查 basename 是否重复注册如果同一个 basename 被注册两次会抛出ImproperlyConfigured异常。在 tests/test_routers.py 中有一组专门的测试覆盖了自动生成的 basename 冲突显式指定 basename 冲突等场景注册时保证 basename 唯一是路由能够正确 reverse 的前提。1.2 空前缀路由路由器还支持注册空前缀prefix这在把整个 API 挂在根路径下时很有用。此时 get_urls 会做特殊处理去掉正则开头的^/regex 模式或开头的/path 模式避免生成//形式的 URL。tests/test_routers.py中的empty_prefix_routertests/test_routers.py#L125-L126即演示了这种用法。二、把路由接入 URLconf 的多种方式router.urls本质上就是一个标准的 URL pattern 列表因此接入方式非常灵活。2.1 追加到现有列表router routers.SimpleRouter() router.register(rusers, UserViewSet) router.register(raccounts, AccountViewSet) urlpatterns [ path(forgot-password/, ForgotPasswordFormView.as_view()), ] urlpatterns router.urls2.2 使用 Django 的includeurlpatterns [ path(forgot-password, ForgotPasswordFormView.as_view()), path(, include(router.urls)), ]2.3 携带应用命名空间urlpatterns [ path(forgot-password/, ForgotPasswordFormView.as_view()), path(api/, include((router.urls, app_name))), ]2.4 同时携带应用命名空间与实例命名空间urlpatterns [ path(forgot-password/, ForgotPasswordFormView.as_view()), path(api/, include((router.urls, app_name), namespaceinstance_name)), ]关于 URL 命名空间的更多细节可以参考 Django 官方的 URL namespaces 文档 与includeAPI 参考。使用命名空间时的重要提醒如果使用了 hyperlinked serializer超链接序列化器view_name参数必须正确反映命名空间。上面的例子中超链接到 user detail 视图的序列化器字段需要写view_nameapp_name:user-detail。另外自动生成的view_name遵循%(model_name)-detail这样的模式。除非模型名确实冲突否则在使用超链接序列化器时通常不建议给 DRF 视图加命名空间——命名空间会让序列化器字段的view_name维护成本明显上升。从测试可以看到命名空间会渗透到 root view 的响应中tests/test_routers.py 中的TestRootView验证了/namespaced/下的 root 响应会为每个资源 URL 带上namespaced:前缀。三、为额外动作extra actions生成路由ViewSet 中所有被action装饰的方法都会自动进入路由系统。例如# views.py from myapp.permissions import IsAdminOrIsSelf from rest_framework.decorators import action class UserViewSet(ModelViewSet): ... action(methods[post], detailTrue, permission_classes[IsAdminOrIsSelf]) def set_password(self, request, pkNone): ...这段代码会生成URL 模式^users/{pk}/set_password/$URL 名称user-set-password默认情况下URL 模式基于方法名生成URL 名称则是ViewSet.basename与连字符化的方法名的组合。如果不想使用默认值可以在action中显式指定url_path与url_namefrom myapp.permissions import IsAdminOrIsSelf from rest_framework.decorators import action class UserViewSet(ModelViewSet): ... action(methods[post], detailTrue, permission_classes[IsAdminOrIsSelf], url_pathchange-password, url_namechange_password) def set_password(self, request, pkNone): ...此时生成的 URL 变为URL 路径^users/{pk}/change-password/$URL 名称user-change_password3.1 action 的源码级行为action装饰器 的核心行为是methods默认只有[get]传入的每个方法名会被小写化detail是必填参数决定动作挂在列表级detailFalse作用于{prefix}/{url_path}/还是详情级detailTrue作用于{prefix}/{lookup}/{url_path}/url_path缺省时取被装饰方法的__name__url_name缺省时取方法名并把下划线替换为连字符其余kwargs会在路由生成时透传给ViewSet.as_view()最终作为每个请求的视图实例属性——这正是permission_classes等参数能按动作生效的原理。此外装饰器还会为方法挂上mapping一个MethodMapper实例允许你为一个动作按 HTTP 方法挂多个处理器class MyViewSet(ViewSet): action(detailFalse) def example(self, request, **kwargs): ... example.mapping.post def create_example(self, request, **kwargs): ...在 tests/test_routers.py 的BasicViewSet中可以看到真实用法action3通过action3.mapping.delete额外注册了action3_delete测试 test_multiple_action_handlers 分别用 POST 与 DELETE 请求验证了同一个 URL 会路由到不同处理器。3.2 动态路由的生成过程从源码看SimpleRouter.get_routes会先收集viewset.get_extra_actions()返回的所有action方法ViewSet.get_extra_actions通过反射收集类中所有被标记的 action 方法然后检查 action 方法名是否与list、create、retrieve等既有路由冲突冲突则抛出ImproperlyConfigured按detail属性把 action 分为详情级与列表级用_get_dynamic_routerouters.py#L212-L224把DynamicRoute模板中的{url_path}、{url_name}占位符替换为每个 action 的真实值并合并 initkwargs。这里有个容易被忽略的细节action 的url_path中的花括号会被 escape_curly_brackets 转义成双花括号{{/}}以免与后续str.format的占位符语法冲突。这意味着你可以在url_path中直接写正则分组如list/(?Pkwarg[0-9]{4})或 path 转换器如detail/int:kwargtests/test_routers.py 中的RegexUrlPathViewSet与UrlPathViewSet正是这种用法。四、使用 Djangopath()而非正则use_regex_path默认情况下路由器生成的 URL 使用正则表达式re_path。把use_regex_path设为False即可改用 Django 的 path 转换器router SimpleRouter(use_regex_pathFalse)在正则模式下lookup 默认匹配除/和.之外的任意字符[^/.]这样既不会吞掉.json这类格式后缀又会在/边界处正确断开。而在 path 模式下默认 lookup 转换器是str。需要更严格或更宽松的 lookup 匹配时可以正则模式在 ViewSet 上设置lookup_value_regexpath 模式在 ViewSet 上设置lookup_value_converter。例如把 lookup 限制为 32 位十六进制字符串或 UUID# 正则模式限制 lookup 为 32 位十六进制 class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): lookup_field my_model_id lookup_value_regex [0-9a-f]{32} # path 模式使用内置的 uuid 转换器 class MyPathModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): lookup_field my_model_uuid lookup_value_converter uuid注意一旦启用 path 转换器它会影响该路由器注册的所有URL包括 ViewSet 上的 action 路由。4.1 底层实现SimpleRouter.__init__根据use_regex_path选择regex 模式self._base_pattern (?P{lookup_prefix}{lookup_url_kwarg}{lookup_value})默认lookup_value为[^/.]URL 构建函数为re_pathpath 模式self._base_pattern {lookup_value}:{lookup_prefix}{lookup_url_kwarg}默认lookup_value为strURL 构建函数为path同时会去掉模板 URL 中的^与$锚点。get_lookup_regex则是组装 lookup 片段的核心lookup 字段默认是pk可被lookup_field覆盖URL kwarg 默认与 lookup 字段同名可被lookup_url_kwarg覆盖path 模式下优先读取lookup_value_converter拿不到再回退到lookup_value_regex最后回退到默认模式。lookup_prefix参数本身在 DRF 内部不直接使用而是为drf-nested-routers这类嵌套路由实现预留的扩展点。对应测试 TestLookupValueRegex 验证了设置lookup_value_regex [0-9a-f]{32}后生成的 URL 模式精确变为^notes/(?Puuid[0-9a-f]{32})/$。五、SimpleRouter 完整路由表SimpleRouter覆盖标准的list、create、retrieve、update、partial_update、destroy六种动作并支持action标记的额外方法URL 样式HTTP 方法动作URL 名称{prefix}/GETlist{basename}-list{prefix}/POSTcreate{basename}-list{prefix}/{url_path}/GET 或methods参数指定的方法action(detailFalse)装饰的方法{basename}-{url_name}{prefix}/{lookup}/GETretrieve{basename}-detail{prefix}/{lookup}/PUTupdate{basename}-detail{prefix}/{lookup}/PATCHpartial_update{basename}-detail{prefix}/{lookup}/DELETEdestroy{basename}-detail{prefix}/{lookup}/{url_path}/GET 或methods参数指定的方法action(detailTrue)装饰的方法{basename}-{url_name}5.1 控制尾斜杠SimpleRouter默认会给所有 URL 追加尾斜杠这与 Django 的惯例一致Rails 等其他框架默认不加。通过trailing_slash参数可以关闭router SimpleRouter(trailing_slashFalse)从 routers.py#L139 可以看到trailing_slashTrue时内部把尾斜杠字符串设为/False时设为然后作为{trailing_slash}占位符填入每条路由的 URL 模板。选择哪种风格更多是个人偏好但需要注意一些前端 JavaScript 框架可能对路由风格有特定预期。相关测试见 tests/test_routers.py#L310-L325。六、DefaultRouter自带 API Root 与格式后缀DefaultRouter在SimpleRouter的基础上额外提供两样东西默认 API root 视图返回一个包含所有 list 视图超链接的响应可选的.json风格格式后缀路由。6.1 DefaultRouter 完整路由表URL 样式HTTP 方法动作URL 名称[.format]GET自动生成的 root 视图api-root{prefix}/[.format]GETlist{basename}-list{prefix}/[.format]POSTcreate{basename}-list{prefix}/{url_path}/[.format]GET 或methods参数指定的方法action(detailFalse)装饰的方法{basename}-{url_name}{prefix}/{lookup}/[.format]GETretrieve{basename}-detail{prefix}/{lookup}/[.format]PUTupdate{basename}-detail{prefix}/{lookup}/[.format]PATCHpartial_update{basename}-detail{prefix}/{lookup}/[.format]DELETEdestroy{basename}-detail{prefix}/{lookup}/{url_path}/[.format]GET 或methods参数指定的方法action(detailTrue)装饰的方法{basename}-{url_name}与SimpleRouter一样可以通过trailing_slash关闭尾斜杠router DefaultRouter(trailing_slashFalse)6.2 真实运行效果下图为使用DefaultRouter注册了users、groups、permissions三个资源后访问 API rootGET /en/得到的真实响应返回体是一个 JSON 对象键为资源前缀值为对应 list 视图的超链接6.3 底层实现细节DefaultRouterrouters.py#L344-L390通过以下方式实现上述能力get_api_root_view遍历self.registry把每个prefix映射到{basename}-list这个 URL 名称然后以api_root_dict为初始化参数实例化APIRootViewAPIRootView.getrouters.py#L322-L341逐个 reverse 列表 URL 名称并组装响应若某个名称无法 reverse例如只有 detail 路由没有 list 路由则跳过而非报错如果请求处于命名空间中reverse 时会自动加上namespace:前缀get_urls在父类生成的 URL 列表基础上追加 root 视图URL 名称为api-root然后调用 format_suffix_patterns 为每条路由补充.format后缀变体。两个可开关的类属性也值得留意include_root_view控制是否包含 root 视图include_format_suffixes控制是否追加格式后缀root_renderers可通过构造参数覆盖默认使用api_settings.DEFAULT_RENDERER_CLASSES。格式后缀的匹配规则在 rest_framework/urlpatterns.py 中定义默认允许[a-z0-9]任意小写字母数字后缀等价于(?Pformat[a-z0-9])并可通过allowed参数限定FORMAT_SUFFIX_KWARG设置决定 URL 中格式参数的关键字名称默认format。七、自定义路由器虽然不常需要但当 API 的 URL 结构有特殊要求时自定义路由器可以把 URL 结构封装成可复用的组件避免为每个新 ViewSet 手写 URL 模式。最简捷的方式是继承现有路由器类并重写.routes属性——它是一组Route命名元组的列表充当每个 ViewSet 的 URL 模板。7.1 Route 命名元组Route的字段定义见 routers.py#L30Route(url, mapping, name, detail, initkwargs)各参数含义如下url要路由的 URL 字符串可包含以下格式占位符{prefix}—— 这组路由使用的 URL 前缀{lookup}—— 匹配单个实例的 lookup 字段{trailing_slash}—— 根据trailing_slash参数为/或空字符串。mappingHTTP 方法名到视图方法的映射字典。namereverse调用中使用的 URL 名称可包含以下格式占位符{basename}—— 生成的 URL 名称所使用的基底。initkwargs实例化视图时传入的附加参数字典。注意detail、basename、suffix是留给 ViewSet 自省用的保留参数浏览式 API 也会用它们来生成视图名称与面包屑链接。7.2 DynamicRoute 命名元组动态路由即action生成的路由通过DynamicRoute定制其字段定义为DynamicRoute(url, name, detail, initkwargs)routers.py#L31。除detail外url与Route.url相同额外支持{url_path}占位符。namereverse中使用的 URL 名称可包含以下格式占位符{basename}—— 生成的 URL 名称所使用的基底{url_name}—— 传给action的url_name。initkwargs实例化视图时传入的附加参数。7.3 示例一个不带尾斜杠的只读路由器下面这个CustomReadOnlyRouter只路由list与retrieve两个动作且不使用尾斜杠from rest_framework.routers import Route, DynamicRoute, SimpleRouter class CustomReadOnlyRouter(SimpleRouter): A router for read-only APIs, which doesnt use trailing slashes. routes [ Route( urlr^{prefix}$, mapping{get: list}, name{basename}-list, detailFalse, initkwargs{suffix: List} ), Route( urlr^{prefix}/{lookup}$, mapping{get: retrieve}, name{basename}-detail, detailTrue, initkwargs{suffix: Detail} ), DynamicRoute( urlr^{prefix}/{lookup}/{url_path}$, name{basename}-{url_name}, detailTrue, initkwargs{} ) ]看看它为下面的 ViewSet 生成了哪些路由。views.pyclass UserViewSet(viewsets.ReadOnlyModelViewSet): A viewset that provides the standard actions queryset User.objects.all() serializer_class UserSerializer lookup_field username action(detailTrue) def group_names(self, request, pkNone): Returns a list of all the group names that the given user belongs to. user self.get_object() groups user.groups.all() return Response([group.name for group in groups])urls.pyrouter CustomReadOnlyRouter() router.register(users, UserViewSet) urlpatterns router.urls生成的路由映射如下URLHTTP 方法动作URL 名称/usersGETlistuser-list/users/{username}GETretrieveuser-detail/users/{username}/group_namesGETgroup_namesuser-group-names注意由于url模板中没有{trailing_slash}占位符这套路由天然不带尾斜杠initkwargs中的suffix会随视图实例化传入用于浏览式 API 的视图命名。想查看更多.routes设置的例子可以直接阅读 SimpleRouter 类的源码——它内置的 list、detail、两个 DynamicRoute 四条模板就是最佳范本。7.4 完全自定义继承 BaseRouter如果连.routes模板机制都无法满足需求可以继承BaseRouter并重写get_urls(self)方法。该方法应检查所有已注册的 ViewSet 并返回 URL pattern 列表已注册的(prefix, viewset, basename)三元组可以通过self.registry属性访问routers.py#L48-L90。通常还需要重写get_default_basename(self, viewset)或者在注册 ViewSet 时始终显式传入basename。BaseRouter.urls属性本身做了结果缓存首次访问时调用get_urls()并缓存之后再次register()会主动使缓存失效routers.py#L63-L65因此注册后再访问 urls的顺序是安全的——这在 test_register_after_accessing_urls 中有直接验证。八、第三方扩展生态中还有一些第三方包可以进一步扩展路由能力这些属于社区项目使用前请自行评估维护状态drf-nested-routers提供路由器和关系字段用于处理嵌套资源如/users/{pk}/posts/。wq.db 的 ModelRouter一个扩展了DefaultRouter的高级路由器类含单例实例提供register_model()API用法与 Django admin 的admin.site.register类似——只需传入模型类URL 前缀、序列化器、ViewSet 都会从模型和全局配置中推断from wq.db import rest from myapp.models import MyModel rest.router.register_model(MyModel)DRF-extensions提供用于创建嵌套 ViewSet、集合级控制器collection level controllers、可自定义端点名称的路由器。九、要点速查一个路由器 一行register() 一组自动生成的 RESTful URLURL 名称统一遵循{basename}-list/{basename}-detail/{basename}-{url_name}约定ViewSet 缺少queryset时必须显式传basename且 basename 在路由器内必须唯一action控制额外动作detail决定列表级还是详情级url_path/url_name覆盖默认 URL 与名称MethodMapper可为同一动作绑定多个 HTTP 方法处理器trailing_slashFalse关闭尾斜杠use_regex_pathFalse改用 Django path 转换器lookup 匹配可通过lookup_value_regex正则模式或lookup_value_converterpath 模式精确控制DefaultRouter比SimpleRouter多了 API root 视图与.format格式后缀可通过include_root_view/include_format_suffixes开关控制自定义路由器的两条路径继承SimpleRouter重写.routesRouteDynamicRoute命名元组或继承BaseRouter重写get_urls()完全接管 URL 生成。十、延伸阅读ViewSet 与 action 标记额外动作的完整说明路由器的核心源码实现路由器的完整测试用例含 basename 冲突、lookup 正则、path 转换器、命名空间等场景格式后缀路由的实现DefaultRouter 的 .format 后缀来源action 装饰器与 MethodMapper 的实现【免费下载链接】django-rest-frameworkWeb APIs for Django. 项目地址: https://gitcode.com/gh_mirrors/dj/django-rest-framework创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询