当项目路由堆到50行以上,你有没有发现改个URL路径要翻半天?团队协作时频繁冲突,新成员看不懂哪个URL属于哪个功能模块——这种体验,但凡经历过Django项目从几个view膨胀到十几个app的过程,应该都不陌生。Django的include()不是“可选技巧”,而是应对真实项目规模的必要分层手段。

为什么直接在主urls.py里写所有路由会出问题
当你把所有路由堆在一个文件里,超过50行就会开始出现几个实际困扰:改一个路径要翻半天、团队协作时频繁冲突、新成员看不懂哪个URL属于哪个功能模块。
怎么用include()把路由拆到各应用里
核心就两步:在每个应用下新建urls.py,再在主urls.py中用include()引入。注意不是import模块,而是传路径字符串给include()。
常见错误现象:NoReverseMatch或404——多半是忘了在子urls.py里加app_name,或者include()时没带命名空间。
- 每个应用的
urls.py开头必须定义app_name = 'blog'(值要和应用名一致) - 主
urls.py中写path('blog/', include('blog.urls', namespace='blog')) include()第二个参数namespace必须显式传,否则{% url 'post_detail' %}这类模板调用会失败- 子路由里的
path()不需要重复写前缀,比如blog/已由主路由定义,子路由只写path('posts//', ...)
include()的参数陷阱和兼容性细节
Django 2.0+要求子urls.py返回一个urlpatterns列表,且必须是模块级变量;老版本允许返回函数,现在会报TypeError: 'function' object is not iterable。
另一个易忽略点:如果子路由用了re_path()或正则捕获组,而主路由又套了一层path()前缀(比如path('api/v1/', include(...))),那正则中的^和$会导致匹配失败——因为include()会把前缀拼进完整路径,正则应去掉行首行尾锚点。
- 子
urls.py必须有urlpatterns = [...],不能只写函数 - 避免在子路由中用
^和$,除非你明确在做全路径匹配 include()支持传入kwargs,可用于向所有子视图注入公共参数(如company_id),但慎用——会增加调试难度
什么时候不该用include()拆分
单应用小项目(比如只有home和contact两个页面)、原型验证阶段、或路由逻辑高度耦合(比如多个应用共用同一组动态前缀),硬拆反而增加跳转层级和维护成本。
更隐蔽的问题是过度拆分:为每个模型建一个应用,每个应用再配一套urls.py,结果导致include('user_profile.urls')、include('user_settings.urls')、include('user_api.urls')并列存在——这其实暴露的是领域建模问题,不是路由问题。
真正该警惕的信号是:你开始给include()写嵌套三层以上,或者在模板里频繁写{% url 'user_profile:settings:api:sa ve' %}这种长命名空间。