1. Django路由作用
连接视图和用户请求的重要桥梁。
2. Django路由类型
- path:用于普通路径,不需要自己手动添加正则首位限制符号,底层已经添加。
- re_path:用于正则路径,需要自己手动添加正则首位限制符号。
普通路径:
# 无动态段
urlpatterns = [
path('index/', views.indexUsers, name='user-detail'),
]
# 有动态段,无转换器
urlpatterns = [
path('articles/<year>/<month>/', views.article_archive, name='article-archive'),
]
# 视图中可以获取参数 year, mouth
def article_detail(request, year, month):
# 这里可以根据year, month, day和title来进行数据库查询或其他操作
pass
# 有动态段,有转换器
urlpatterns = [
path('articles/<int:year>/<str:month>/', views.article_archive, name='article-archive'),
]
# 视图中可以获取参数 year, mouth
def article_detail(request, year, month):
# 这里可以根据year, month, day和title来进行数据库查询或其他操作
pass
正则路径:
# 无命名参数
urlpatterns = [
re_path("^index5/([0-9]{4})/([0-9]{2})/$", views.Regular.as_view()),
]
class Regular(View):
def get(self, request, year, month):
print(year, month)
return HttpResponse('路由无名参数')
# 命名参数
urlpatterns = [
re_path("^index6/(?P<year1>[0-9]{4})/(?P<month>[0-9]{2})/$", views.Regular6.as_view()),
]
# 注意参数要和一致
class Regular6(View):
def get(self, request, year1, month):
print(year1, month)
return HttpResponse('路由有名参数')
3.反向解析URL
反向解析是指通过视图名称和参数来动态生成URL的过程。
from django.urls import reverse
def some_view(request):
# ...
url = reverse('my_view_name', args=[1, 2, 3])
# 现在变量url包含'/path/to/my_view_name/1/2/3/'
reverse()
函数:
- 在Python代码(视图、模型方法)中使用。
- 根据 URL 的名称(
name
参数)和可选的参数(kwargs
)生成实际的 URL 字符串。 - 用法:
reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
{% url %}
模板标签:
- 在Django模板中使用。
- 功能与
reverse()
相同,但在模板语法中。 - 用法:
{% url 'view_name' arg1 arg2 kwarg1=value1 %}
命名空间 (app_name
):
- 使用
app_name
可以避免不同应用间 URL 名称冲突。 - 在
reverse()
或{% url %}
中,需要使用'app_name:url_name'
的格式。
# myapp/urls.py
urlpatterns = [
# path('', views.index, name='index'), # 根路径
path('articles/', views.index, name='index'), # 更清晰的路径
path('articles/<int:article_id>/', views.article_detail, name='article_detail'),
path('articles/create/', views.create_article, name='create_article'),
]
# myapp/views.py
def index(request):
"""显示所有文章列表"""
articles = Article.objects.all().order_by('-published_date')
# 示例 1: 在视图中使用 reverse 构造 URL (通常用于重定向)
# 假设我们想在某个条件下重定向到创建文章页面
# create_url = reverse('create_article') # 通过名称获取 URL
# print(f"Create Article URL: {create_url}") # /articles/create/
context = {
'articles': articles,
# 示例 2: 将反向解析的 URL 传递给模板
# 这在需要 JavaScript 动态获取 URL 时很有用
'create_article_url': reverse('create_article'),
}
return render(request, 'myapp/index.html', context)
def article_detail(request, article_id):
"""显示单篇文章详情"""
article = get_object_or_404(Article, id=article_id)
# 示例 3: 在视图中使用 reverse (例如,重定向到详情页后)
# current_url = reverse('article_detail', kwargs={'article_id': article.id})
# print(f"Current Article URL: {current_url}") # /articles/1/
context = {'article': article}
return render(request, 'myapp/detail.html', context)
def create_article(request):
"""创建新文章 (简化版,仅演示 reverse)"""
if request.method == 'POST':
# 这里省略表单处理和验证
title = request.POST.get('title')
content = request.POST.get('content')
if title and content:
article = Article.objects.create(title=title, content=content)
# 示例 4: 使用 reverse 进行重定向 - 这是最常见的用法之一
# 重定向到新创建文章的详情页
detail_url = reverse('article_detail', kwargs={'article_id': article.id})
return redirect(detail_url) # 等同于 redirect('article_detail', article_id=article.id)
# redirect 函数内部也使用了 reverse
return HttpResponse("""
<h1>Create New Article</h1>
<form method="post">
{% csrf_token %}
<label for="title">Title:</label>
<input type="text" name="title" required><br>
<label for="content">Content:</label>
<textarea name="content" required></textarea><br>
<button type="submit">Create</button>
</form>
<a href="{}">Back to List</a>
""".format(reverse('index'))) # 在内联 HTML 中使用 reverse
# myapp/templates/myapp/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Article List</title>
</head>
<body>
<h1>Articles</h1>
<!-- 示例 5: 在模板中使用 url 模板标签 (Django 模板中的 reverse) -->
<a href="{% url 'myapp:create_article' %}">Create New Article</a>
<!-- 注意: 因为定义了 app_name='myapp',所以需要使用 'myapp:create_article' -->
<!-- 如果没有 app_name,则使用 'create_article' -->
<ul>
{% for article in articles %}
<li>
<!-- 示例 6: 使用 url 标签链接到详情页 -->
<a href="{% url 'myapp:article_detail' article_id=article.id %}">
{{ article.title }}
</a>
(Published: {{ article.published_date|date:"M d, Y" }})
</li>
{% empty %}
<li>No articles yet.</li>
{% endfor %}
</ul>
<!-- 示例 7: 使用视图中传递的反向解析 URL (来自 context) -->
<!-- 虽然这里用 url 标签更直接,但展示如何使用传递的 URL -->
<p><a href="{{ create_article_url }}">Create Article (via context)</a></p>
</body>
</html>
4.路由的命名空间
为什么需要命名空间?
想象一下,你有两个应用:blog
和 news
。它们都可能有名为 index
、detail
、create
的视图。如果没有命名空间:
# blog/urls.py
path('', views.index, name='index') # blog 的首页
path('<int:post_id>/', views.detail, name='detail') # blog 的详情页
# news/urls.py
path('', views.index, name='index') # news 的首页
path('<int:article_id>/', views.detail, name='detail') # news 的详情页
当你在代码中使用 reverse('index')
或模板中使用 {% url 'index' %}
时,Django 无法确定你指的是 blog
的 index
还是 news
的 index
,这会导致冲突和不可预测的行为。
命名空间就是为了解决这个问题而生的!
命名空间的类型
Django 支持两种命名空间:
- 应用命名空间 (Application Namespace): 通过
app_name
变量在应用的urls.py
中定义。它标识了 URL 模式属于哪个 Django 应用。 - 实例命名空间 (Instance Namespace): 在项目
urls.py
的include()
函数中通过namespace
参数定义。它标识了应用的一个具体实例。一个应用可以有多个实例。
myproject/
├── blog/ # 博客应用
│ ├── views.py
│ ├── urls.py
│ └── models.py
├── news/ # 新闻应用
│ ├── views.py
│ ├── urls.py
│ └── models.py
└── myproject/
└── urls.py # 项目主 URL 配置
1. 定义应用命名空间 (app_name
)
在每个应用的 urls.py
文件中,设置 app_name
。
blog/urls.py
:
from django.urls import path
from . import views
# 👉 定义应用命名空间
app_name = 'blog'
urlpatterns = [
# 现在这些 URL 名称属于 'blog' 命名空间
path('', views.index, name='index'), # 完整名称: blog:index
path('<int:post_id>/', views.detail, name='detail'), # 完整名称: blog:detail
path('create/', views.create, name='create'), # 完整名称: blog:create
]
news/urls.py
:
from django.urls import path
from . import views
# 👉 定义应用命名空间
app_name = 'news'
urlpatterns = [
# 这些 URL 名称属于 'news' 命名空间
path('', views.index, name='index'), # 完整名称: news:index
path('<int:article_id>/', views.detail, name='detail'), # 完整名称: news:detail
path('create/', views.create, name='create'), # 完整名称: news:create
]
2. 在项目 URL 中包含应用 (可选:定义实例命名空间)
在项目主 urls.py
中,使用 include()
包含应用的 URL 配置。
myproject/urls.py
:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# 方法 1: 只包含,不指定实例命名空间
# 此时,实例命名空间默认等于应用命名空间 ('blog' 和 'news')
path('blog/', include('blog.urls')), # 实例命名空间: 'blog'
path('news/', include('news.urls')), # 实例命名空间: 'news'
# 方法 2: 显式指定不同的实例命名空间 (高级用法)
# path('my-blog/', include('blog.urls', namespace='personal_blog')), # 实例命名空间: 'personal_blog'
# path('company-news/', include('news.urls', namespace='corporate_news')), # 实例命名空间: 'corporate_news'
]
注意: 如果在
include()
中没有指定namespace
参数,Django 会使用app_name
的值作为实例命名空间。
如何使用命名空间
一旦定义了命名空间,你就可以在代码和模板中精确地引用 URL。
在 Python 代码中使用 reverse()
from django.urls import reverse
from django.shortcuts import redirect
def some_view(request):
# ✅ 正确:使用完整的命名空间名称
blog_index_url = reverse('blog:index') # 结果: '/blog/'
news_detail_url = reverse('news:detail', kwargs={'article_id': 5}) # 结果: '/news/5/'
blog_create_url = reverse('blog:create') # 结果: '/blog/create/'
# 🔁 重定向到博客首页
return redirect('blog:index') # 等同于 redirect(reverse('blog:index'))
# 🔁 重定向到新闻详情页
# return redirect('news:detail', article_id=10)
在 Django 模板中使用 {% url %}
标签
<!-- templates/some_template.html -->
<!DOCTYPE html>
<html>
<head>
<title>My Site</title>
</head>
<body>
<h1>Welcome!</h1>
<!-- ✅ 正确:使用命名空间 -->
<a href="{% url 'blog:index' %}">Go to Blog</a> <!-- 生成: /blog/ -->
<a href="{% url 'news:index' %}">Go to News</a> <!-- 生成: /news/ -->
<!-- 带参数的 URL -->
<a href="{% url 'blog:detail' post_id=1 %}">Read Blog Post 1</a> <!-- 生成: /blog/1/ -->
<a href="{% url 'news:detail' article_id=2 %}">Read News Article 2</a> <!-- 生成: /news/2/ -->
<!-- 创建新内容 -->
<a href="{% url 'blog:create' %}">Write a Blog Post</a> <!-- 生成: /blog/create/ -->
<a href="{% url 'news:create' %}">Submit News</a> <!-- 生成: /news/create/ -->
<!-- ❌ 错误:不使用命名空间 (如果存在冲突,行为不确定) -->
<!-- <a href="{% url 'index' %}">This might not work as expected!</a> -->
</body>
</html>
实例命名空间的高级用法
假设你想为同一个 blog
应用创建两个不同的实例(例如,个人博客和公司博客)。
myproject/urls.py
(修改):
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# 👉 为同一个应用创建两个不同实例,使用不同的实例命名空间
path('personal/', include('blog.urls', namespace='personal_blog')), # 实例命名空间: personal_blog
path('company/', include('blog.urls', namespace='company_blog')), # 实例命名空间: company_blog
]
在代码中使用实例命名空间:
# Python 代码
personal_blog_index = reverse('personal_blog:index') # 结果: '/personal/'
company_blog_index = reverse('company_blog:index') # 结果: '/company/'
# 模板
<a href="{% url 'personal_blog:index' %}">My Personal Blog</a> <!-- 生成: /personal/ -->
<a href="{% url 'company_blog:index' %}">Company Blog</a> <!-- 生成: /company/ -->
重要: 当使用实例命名空间时,
reverse()
会优先查找实例命名空间。如果找不到,它会回退到应用命名空间。
命名空间查找规则
当你调用 reverse(viewname, current_app=None)
或 {% url 'viewname' %}
时,Django 按以下顺序查找:
current_app
参数: 如果提供了current_app
,Django 会首先在该应用实例的命名空间中查找。- 实例命名空间: 如果没有
current_app
,Django 会根据当前请求的 URL 匹配到的include()
的namespace
来查找。 - 应用命名空间: 如果实例命名空间未找到或未定义,Django 会回退到
app_name
定义的应用命名空间。 - 无命名空间: 最后,如果以上都失败,才会查找无命名空间的 URL 名称。
最佳实践与总结
- 始终使用
app_name
: 为每个应用的urls.py
文件定义app_name
。这是避免命名冲突的最基本和最重要的步骤。 - 在模板和代码中使用完整名称: 始终使用
'app_name:url_name'
的格式(如'blog:index'
)来引用 URL。 - 实例命名空间用于高级场景: 通常情况下,
include('app.urls')
就足够了,实例命名空间默认等于app_name
。只有在需要部署同一个应用的多个独立实例时才显式使用namespace
参数。 - 提高可维护性: 命名空间使你的代码更清晰。看到
'blog:create'
就知道这是博客应用的创建页面,而不会与'news:create'
混淆。 - 解耦应用: 应用可以独立开发,只要定义好自己的
app_name
和 URL 名称,集成到项目中时就不会轻易产生冲突。
通过使用命名空间,你可以构建出结构清晰、易于维护且可扩展的 Django 项目。
如果 url 有多层 include 嵌套,要怎么写?
只要记住一句话:
用“冒号”一级一级拼出完整的“实例命名空间链”,最后才是应用命名空间 + 路由名。
顺序永远是:
最外层实例 : 次外层实例 : … : 应用命名空间 : 路由名
✅ 举个 3 层嵌套的例子
目录结构示意
mysite/
urls.py # 根
user/
urls.py # 第 1 层 include
blog/
urls.py # 第 2 层 include
article/
urls.py # 第 3 层,真正放路由
1) 根 urls.py(第 0 层)
# mysite/urls.py
from django.urls import path, include
urlpatterns = [
path('user/', include('user.urls', namespace='u')), # 实例 u
]
2) user/urls.py(第 1 层)
# user/urls.py
from django.urls import path, include
urlpatterns = [
path('blog/', include('blog.urls', namespace='b')), # 实例 b
]
3) blog/urls.py(第 2 层)
# blog/urls.py
from django.urls import path, include
urlpatterns = [
path('article/', include('article.urls')), # 没有新的 namespace
]
4) article/urls.py(第 3 层,真正路由)
# article/urls.py
from django.urls import path
from . import views
app_name = 'article' # 应用命名空间
urlpatterns = [
path('<int:pk>/', views.detail, name='detail'),
]
✅ reverse 写法(在 Python 代码里)
from django.urls import reverse
url = reverse('u:b:article:detail', args=[2024])
# 结果:/user/blog/article/2024/
-
u
—— 最外层实例(根 include) -
b
—— 次外层实例(user include) -
article
—— 应用命名空间(article/urls.py 里的app_name
) -
detail
—— 路由名
✅ 模板里
{% url 'u:b:article:detail' 2024 %}
如果某一级没有 namespace
?
-
没有
namespace
的那一级 不会出现在链里。 -
例子中
blog/urls.py
再 includearticle
时没给namespace
,所以链里直接跳到article
。
速记口诀
“实例一路冒号拼,最后是 app_name:路由名”。
实例命名空间 有什么用? 什么时候必须设置?
一句话:
实例命名空间 = 给同一份 URLconf 起“不同的名字”,让 Django 在反向解析时能分清“到底用的是哪一份”。
它只在 同一份 URLconf 被 include 多次 时才“必须设置”,否则可以不设。
1. 什么时候“必须设置”?
场景 | 是否必须设置实例命名空间 |
---|---|
同一个 app 的 urls.py 只被 include 一次 | ❌ 不必设置 |
同一个 app 的 urls.py 被 include 多次(例如多语言、多站点、多租户) | ✅ 必须设置,否则 reverse() / {% url %} 无法区分 |
2. 没有实例命名空间会怎样?
假设你有:
# blog/urls.py
app_name = 'blog'
urlpatterns = [
path('article/<int:pk>/', views.detail, name='article-detail'),
]
# 主 urls.py
urlpatterns = [
path('en/', include('blog.urls')), # 想给英文站点
path('zh/', include('blog.urls')), # 想给中文站点
]
-
两个 include 都没给
namespace
,都叫同一个应用命名空间blog
。 -
反向解析
reverse('blog:article-detail', args=[1])
时,Django 不知道应该返回/en/article/1/
还是/zh/article/1/
,于是抛NoReverseMatch
。
3. 加了实例命名空间就安全
urlpatterns = [
path('en/', include('blog.urls', namespace='en_blog')),
path('zh/', include('blog.urls', namespace='zh_blog')),
]
-
现在有两条实例命名空间:
en_blog
、zh_blog
。 -
反向解析可以明确写:
reverse('en_blog:article-detail', args=[1]) # /en/article/1/
reverse('zh_blog:article-detail', args=[1]) # /zh/article/1/
4. 小结
概念 | 作用 | 什么时候必须出现 |
---|---|---|
应用命名空间 (app_name ) | 让同一 app 的所有路由共享一个“逻辑组名” | 只要想在 reverse() 里用 app:route 形式,就必须写 |
实例命名空间 (namespace=xxx ) | 当同一份应用被部署多次时,给每一次 include 起独一无二的名字 | 同一份 URLconf 被 include 多次时必须设置,否则反向解析冲突 |
一句话:
“只有同一份 URLconf 被 include 多次,才需要实例命名空间来区分;否则只保留应用命名空间即可。”
实例命名空间 、 应用命名空间, 哪个是必须的?
在 Django 的 URL 反向解析体系里,必须有“应用命名空间”(app_name) 这一级,否则 reverse()
/{% url %}
无法工作。
“实例命名空间”(namespace) 只有在“同一份 URLconf 被 include 多次”时才额外出现,它并不能取代 app_name
。
实验验证
-
把
blog/urls.py
写成:
# blog/urls.py —— 故意不写 app_name
from django.urls import path
from . import views
urlpatterns = [
path('article/<int:pk>/', views.detail, name='article-detail'),
]
-
在主路由中:
# 只给 namespace,不给 app_name
urlpatterns = [
path('en/', include('blog.urls', namespace='en')),
]
-
视图或模板里尝试:
reverse('en:article-detail', args=[1])
# 或
{% url 'en:article-detail' 1 %}
结果:
Django 抛出 ImproperlyConfigured: App with label 'en' could not be found. …
(或 NoReverseMatch
),
因为它找不到名为 article-detail
的 应用命名空间。
正确姿势
# blog/urls.py
app_name = 'blog' # 必须给
urlpatterns = [
path('article/<int:pk>/', views.detail, name='article-detail'),
]
# 主 urls.py
urlpatterns = [
path('en/', include('blog.urls', namespace='en')), # namespace 可选
]
反向解析:
reverse('en:blog:article-detail', args=[1]) # 有实例
# 或
reverse('blog:article-detail', args=[1]) # 无实例
结论
-
应用命名空间 (
app_name
) 是必填项; -
实例命名空间 (
namespace
) 只是可选的“区分符”; -
没有
app_name
,任何reverse()
/{% url %}
都会失败。 -
因为优先查找,实例命名空间,
reverse()参数可以用 实例命名空间,不写app_name