Chapter 02

应用与路由 URLconf

用 startapp 拆分功能单元,用 URLconf 把请求路径分发到视图,掌握 path、include、命名 URL 与 reverse。

2.1 创建 App

Django 项目由多个 App 组成,每个 App 聚焦一个功能领域。用 startapp 生成骨架:

$ python manage.py startapp blog

blog/
├── __init__.py
├── admin.py         # 后台注册
├── apps.py          # App 配置
├── models.py        # 数据模型
├── views.py         # 视图逻辑
├── tests.py
└── migrations/

创建后必须在 settings.py 里注册:

INSTALLED_APPS = [
    # ... 内置 App
    "blog",      # 或 "blog.apps.BlogConfig"
]
URLconf(URL configuration)
Django 的路由表,用 Python 列表 urlpatterns 把 URL 模式映射到视图。每个 App 通常有自己的 urls.py
path()
定义一条路由:path(route, view, name=...)。route 用简洁的路径转换器语法,如 <int:pk>
re_path()
用正则表达式匹配 URL,适合 path 转换器表达不了的复杂模式。
include()
把某个前缀下的路由委派给 App 的 urls.py,实现路由的模块化组合。
路径转换器(Path Converter)
str/int/slug/uuid/path。捕获的值作为关键字参数传给视图。
命名 URL(Named URL)
给路由起 name,在代码/模板中用名字反查真实 URL,避免硬编码路径。
reverse()
根据 URL 名字与参数反向生成 URL 字符串,是「命名 URL」在 Python 侧的用法。

2.2 第一个视图函数

# blog/views.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, 古法编程 · Django 博客首页")

def detail(request, post_id):
    return HttpResponse(f"你正在看第 {post_id} 篇文章")

2.3 App 内的 urls.py

# blog/urls.py(需手动创建)
from django.urls import path
from . import views

app_name = "blog"      # 命名空间

urlpatterns = [
    path("", views.index, name="index"),
    path("post/<int:post_id>/", views.detail, name="detail"),
]

2.4 用 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")),
]

现在 /blog/ 命中 index,/blog/post/5/ 命中 detail 并把 5 作为 post_id 传入。

2.5 路径转换器

转换器匹配示例
str非空非斜杠字符串(默认)<str:slug>
int零或正整数<int:pk>
slug字母数字连字符<slug:title>
uuidUUID 格式<uuid:id>
path含斜杠的字符串<path:rest>

2.6 re_path 正则路由

from django.urls import re_path

urlpatterns = [
    re_path(r"^archive/(?P<year>[0-9]{4})/$", views.archive),
]
优先用 path

path 转换器可读性远高于正则。仅当需要精确控制格式(如四位年份、特定后缀)时才用 re_path。

2.7 命名 URL 与 reverse

from django.urls import reverse
from django.shortcuts import redirect

# 反查 URL(带命名空间 app_name:name)
url = reverse("blog:detail", kwargs={"post_id": 5})
# => "/blog/post/5/"

# 视图里重定向到命名 URL
def go(request):
    return redirect("blog:index")

在模板中同理,用 {% url %} 标签:

<a href="{% url 'blog:detail' post.id %}">{{ post.title }}</a>
永远不要硬编码 URL

/blog/post/5/ 写死在模板里,一旦路由结构变动就得全站搜索替换。始终用命名 URL + reverse/{% url %},改路由零成本。

2.8 小结与下一步