CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Django 实战入门/课时/URL 路由
RawBlame
课时目录12 节

Django 实战入门

  1. 01Django 概览
  2. 02项目结构
  3. 03URL 路由
  4. 04视图层
  5. 05模型与 ORM
  6. 06查询与优化
  7. 07模板 DTL
  8. 08Admin 与认证
  9. 09表单与安全
  10. 10DRF 与异步任务
  11. 11测试与部署
  12. 12综合练习

URL 路由

path、命名 URL 与 reverse。

30 分钟

URL 路由

这一课你将能做到

  1. 用 path() 写出带类型转换器、可选参数和正则匹配的路由
  2. 用 name 给路由命名,并在视图/模板中通过 reverse() 和 {% url %} 反查 URL
  3. 用 include() 把 URL 按 app 拆分,让项目根路由保持干净

为什么要学

URL 路由是 Web 框架的「门面」。用户输入什么地址,服务器执行什么代码,全由路由决定。Django 的路由系统虽然不像 Flask/FastAPI 那样用装饰器直接注册,但借助 urls.py 的显式声明,反而在大项目里更容易定位、排查和做 URL 全局替换。

对比 Spring Boot:Spring 用 @GetMapping("/users/{id}") 注解在控制器上声明;Django 把路由抽成独立的 urls.py,控制器(View)只写业务逻辑。两种思路:Java 是「控制器在哪里,路由就在哪里」;Django 是「路由集中管理,视图只管响应」。

先建立图像

请求 GET /articles/2024/python-orm/
                │
                ▼
       urls.py 逐条匹配
       path("articles/<int:year>/<slug:slug>/", views.article_detail)
                │
                ▼
    views.article_detail(request, year=2024, slug="python-orm")

跟着做

步骤 1:基础 path 与转换器

blog/urls.py:

from django.urls import path
from . import views

app_name = "blog"

urlpatterns = [
    path("", views.index, name="index"),
    path("<int:year>/", views.year_archive, name="year_archive"),
    path("<int:year>/<int:month>/", views.month_archive, name="month_archive"),
    path("<int:year>/<int:month>/<slug:slug>/", views.article_detail, name="detail"),
]

blog/views.py:

from django.http import HttpResponse


def index(request):
    return HttpResponse("首页")


def year_archive(request, year):
    return HttpResponse(f"{year} 年归档")


def month_archive(request, year, month):
    return HttpResponse(f"{year} 年 {month} 月归档")


def article_detail(request, year, month, slug):
    return HttpResponse(f"文章:{year}/{month}/{slug}")

int 和 slug 是 Django 自带的 path converter:int 匹配正整数;slug 匹配 ASCII 字母/数字/连字符/下划线,常用于 URL 友好的文章标题缩写。

步骤 2:自定义转换器

如果内置转换器不够用,你可以自己写。例如:匹配 4 位年份:

# blog/converters.py
class FourDigitYearConverter:
    regex = r"[0-9]{4}"

    def to_python(self, value):
        return int(value)

    def to_url(self, value):
        return f"{value:04d}"

注册:

# blog/urls.py
from django.urls import path, register_converter
from . import converters, views

register_converter(converters.FourDigitYearConverter, "yyyy")

app_name = "blog"

urlpatterns = [
    path("archives/<yyyy:year>/", views.year_archive, name="year_custom"),
]

to_python 把 URL 段转成 Python 值;to_url 在反向解析时把值转成字符串。

步骤 3:命名 URL 与 reverse

路由一旦命名,你就可以在代码里不硬编码 URL:

from django.urls import reverse
from django.http import HttpResponseRedirect


def redirect_to_home(request):
    # /blog/ 这个 URL 将来改成 /news/ 也不怕
    url = reverse("blog:index")
    return HttpResponseRedirect(url)

带参数:

reverse("blog:detail", kwargs={"year": 2024, "month": 8, "slug": "django-urls"})
# /blog/2024/8/django-urls/

模板里反查:

<a href="{% url 'blog:index' %}">首页</a>
<a href="{% url 'blog:detail' year=2024 month=8 slug='django-urls' %}">文章</a>

步骤 4:re_path 与正则路由

旧项目或需要复杂匹配时用 re_path():

from django.urls import re_path

urlpatterns = [
    re_path(r"^articles/(?P<year>[0-9]{4})/$", views.year_archive),
    re_path(r"^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$", views.month_archive),
]

注意 ^ 和 $ 不可省略,否则 /articles/2024/ 也会匹配 /articles/2024/extra-stuff/。

步骤 5:路由分发与 include

mysite/urls.py:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("blog/", include("blog.urls")),
    path("shop/", include("shop.urls")),
]

被 include 的 blog/urls.py 会获得 /blog/ 作为前缀。这样 app 只管自己的子路由,项目只管分发前缀。

include() 的第二个参数传 namespace 可以定义反向解析时的命名空间,但在被 include 的文件里定义 app_name 是更推荐的方式。

讲清楚要点

  1. path() vs re_path():Django 2.0 起推荐 path(),更直观、转换器自带类型检查。只有复杂正则需求才用 re_path()。
  2. 五个内置转换器:str(默认,非空字符串不含 /)、int、slug、uuid、path(匹配任意字符含 /)。
  3. 命名空间避免撞车:blog:index 和 shop:index 不会冲突。不写 app_name 也能跑,但写项目级的 include(namespace="...") 做反向解析容易混淆。
  4. reverse() 不只是省事:它是 Django 路由「单一事实来源」思想的体现。URL 地址变一处,所有引用自动跟着变,不遗漏。
  5. 路由匹配是「先来先服务」:urlpatterns 是有序列表,把更具体的路由放上面,通用路由放下面。放反了会导致特定路由永远匹配不上。
  6. 404 不是路由的责任:如果路由匹配成功但视图里查不到数据(比如 id=999 的文章),视图应该返回 Http404 而非让路由背锅。

你可能会懵的地方

  • <int:pk> 和 <str:name> 的顺序? Django 从上到下匹配,先匹配到的优先。如果同时有 <str:name>/ 和 <int:pk>/,把 int 放前面,否则 int 的值会被 str 吞掉。

  • reverse 和 resolve 区别? reverse:给名字 + 参数 → 生成 URL 字符串。resolve:给 URL 字符串 → 反查哪个视图处理。一个正向生成,一个反向诊断。

  • path("", views.index) 里的空字符串是什么意思? 表示「当前前缀到此结束」。例如被 path("blog/", include("blog.urls")) 分发后,blog.urls 里的 path("", ...) 对应 /blog/。

  • 正则路由捕获的值都是字符串吗? 对。re_path 捕获后不会自动转 int,要在视图里自己 int(year)。而 path("<int:year>") 会自动转。

  • 能不能给路由加 HTTP 方法限制? Django 路由本身不限方法,在视图里根据 request.method 分发。需要装饰器风格限制可用 @require_http_methods(["GET", "POST"])(从 django.views.decorators.http 引入)。

错法对照

错法问题正解
path("<int:year>/<slug:slug>") 无尾部 /Django 默认重定向补 /,导致 301URL 统一以 / 结尾
reverse("detail", args=[2024, 8]) 少传参数TypeError用 kwargs 或确保 args 数量和顺序对
urlpatterns 里把通用路由放最前面具体路由永远匹配不到从具体到抽象,从窄到宽
re_path(r"articles/(?P<year>[0-9]+)") 没写 ^$只匹配前缀,会吞子路径始终加 ^ 和 $
app_name = "blog" 加在项目根 urls.pyapp_name 属于 app 级路由放在被 include 的 app/urls.py 里

卡点急救

  1. NoReverseMatch 异常:通常是名字拼错、参数缺失、或 namespace 没对上。先 python manage.py show_urls(装 django-extensions)查看所有路由和名字。
  2. 访问带参数的 URL 返回 404:转换器没匹配上。比如 /blog/abc/ 走 <int:year> 匹配失败。检查 URL 实际输入的值。
  3. include() 之后子路由的 app_name 不生效:include() 时传了 namespace 参数,但子 urls.py 没定义 app_name,Django 会抛异常。两者必须配对使用。

面试常问

  • Django 路由和 Spring Boot 的路由注解有什么区别? Django 是集中式配置(urls.py),路由与视图解耦,利于全局审计和批量替换。Spring Boot 是注解式(@GetMapping),路由跟着控制器走,直观但散落在类里。Django 更利于 URL 全局重构,Spring Boot 更利于读单个文件时一目了然。

  • 如何实现一个必须匹配 4 位年份的路由? 最推荐自定义 converter(FourDigitYearConverter),次选 re_path(r"^articles/(?P<year>[0-9]{4})/$")。converter 的好处是可以在多个地方复用,且 to_python 保证类型安全。

  • Django 路由匹配的顺序是什么? 按 urlpatterns 列表顺序从上到下,第一个匹配的 view 被调用。因此需要把更具体的规则放在前面,兜底规则(如 catch-all)放最后。

自测三问

  1. 内置的五个 path converter 分别是什么,适用场景?
  2. 为什么推荐给路由命名?reverse() 在项目中解决了什么问题?
  3. 自定义 converter 时,to_python 和 to_url 分别在哪一步被调用?

小结一句话

Django 路由系统用显式的 urls.py 集中管理所有 URL 映射,配合命名、反查和 include() 分发,让大项目的 URL 全局替换和跨 app 复用变得安全。

延伸

  • Django 官方文档 - URL dispatcher
  • 下一课:视图层——FBV、CBV、Generic Views 的取舍。
加载本节练习…
← 上一课项目结构下一课 →视图层