Django路由学习笔记
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.pydef 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 函数内部也使用了 reversereturn 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:indexpath('<int:post_id>/', views.detail, name='detail'), # 完整名称: blog:detailpath('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:indexpath('<int:article_id>/', views.detail, name='detail'), # 完整名称: news:detailpath('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, includeurlpatterns = [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 redirectdef 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, includeurlpatterns = [path('admin/', admin.site.urls),# 👉 为同一个应用创建两个不同实例,使用不同的实例命名空间path('personal/', include('blog.urls', namespace='personal_blog')), # 实例命名空间: personal_blogpath('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, includeurlpatterns = [path('user/', include('user.urls', namespace='u')), # 实例 u
]
2) user/urls.py(第 1 层)
# user/urls.py
from django.urls import path, includeurlpatterns = [path('blog/', include('blog.urls', namespace='b')), # 实例 b
]
3) blog/urls.py(第 2 层)
# blog/urls.py
from django.urls import path, includeurlpatterns = [path('article/', include('article.urls')), # 没有新的 namespace
]
4) article/urls.py(第 3 层,真正路由)
# article/urls.py
from django.urls import path
from . import viewsapp_name = 'article' # 应用命名空间
urlpatterns = [path('<int:pk>/', views.detail, name='detail'),
]
✅ reverse 写法(在 Python 代码里)
from django.urls import reverseurl = 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 viewsurlpatterns = [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