第3章:URL路由系统
3.1 URL配置基础
URLconf概念
URLconf(URL configuration)是Django中URL模式的配置文件,它定义了URL路径与视图函数之间的映射关系。 Django使用URLconf来决定如何处理每个传入的URL请求。
URL处理流程
用户请求URL
↓
Django检查ROOT_URLCONF设置
↓
加载URLconf模块
↓
按顺序检查urlpatterns中的每个URL模式
↓
找到第一个匹配的模式
↓
调用对应的视图函数
↓
返回HttpResponse对象path()函数详解
Django 2.0引入了 path() 函数,提供了更简洁和类型安全的URL配置方式。
基本语法
python
from django.urls import path
from . import views
path(route, view, kwargs=None, name=None)参数说明:
- route:URL模式字符串
- view:视图函数或类视图的as_view()方法
- kwargs:传递给视图的额外参数字典
- name:URL模式的名称,用于反向解析
基本示例
python
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
# 静态URL
path('', views.home, name='home'),
path('about/', views.about, name='about'),
path('contact/', views.contact, name='contact'),
# 带参数的URL
path('article/<int:id>/', views.article_detail, name='article_detail'),
path('category/<str:name>/', views.category_detail, name='category_detail'),
path('tag/<slug:slug>/', views.tag_detail, name='tag_detail'),
# 类视图
path('articles/', views.ArticleListView.as_view(), name='article_list'),
path('search/', views.SearchView.as_view(), name='search'),
]内置路径转换器
Django提供了多种内置路径转换器:
python
# 1. str - 匹配除路径分隔符外的非空字符串(默认)
path('category/<str:name>/', views.category_detail)
# 匹配:/category/technology/
# 不匹配:/category/ 或 /category/tech/sub/
# 2. int - 匹配零或正整数
path('article/<int:id>/', views.article_detail)
# 匹配:/article/123/
# 不匹配:/article/abc/ 或 /article/-1/
# 3. slug - 匹配slug格式字符串(字母、数字、连字符、下划线)
path('post/<slug:slug>/', views.post_detail)
# 匹配:/post/my-first-post/
# 不匹配:/post/my post/ 或 /post/my@post/
# 4. uuid - 匹配UUID格式
path('user/<uuid:user_id>/', views.user_profile)
# 匹配:/user/550e8400-e29b-41d4-a716-446655440000/
# 5. path - 匹配包括路径分隔符的非空字符串
path('file/<path:file_path>/', views.serve_file)
# 匹配:/file/documents/2023/report.pdf实际应用示例
python
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
# 首页
path('', views.home, name='home'),
# 文章相关
path('articles/', views.article_list, name='article_list'),
path('article/<int:id>/', views.article_detail, name='article_detail'),
path('article/<slug:slug>/', views.article_detail_by_slug, name='article_detail_by_slug'),
# 分类和标签
path('category/<slug:category_slug>/', views.category_detail, name='category_detail'),
path('tag/<slug:tag_slug>/', views.tag_detail, name='tag_detail'),
# 归档
path('archive/<int:year>/', views.year_archive, name='year_archive'),
path('archive/<int:year>/<int:month>/', views.month_archive, name='month_archive'),
# 搜索和RSS
path('search/', views.search, name='search'),
path('rss/', views.rss_feed, name='rss_feed'),
# 用户相关
path('author/<str:username>/', views.author_detail, name='author_detail'),
]对应的视图函数:
python
# blog/views.py
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from .models import Article, Category, Tag
from django.contrib.auth.models import User
def home(request):
"""首页视图"""
latest_articles = Article.objects.filter(status='published')[:5]
return render(request, 'blog/home.html', {'articles': latest_articles})
def article_detail(request, id):
"""通过ID获取文章详情"""
article = get_object_or_404(Article, id=id, status='published')
return render(request, 'blog/article_detail.html', {'article': article})
def article_detail_by_slug(request, slug):
"""通过slug获取文章详情"""
article = get_object_or_404(Article, slug=slug, status='published')
return render(request, 'blog/article_detail.html', {'article': article})
def category_detail(request, category_slug):
"""分类详情页"""
category = get_object_or_404(Category, slug=category_slug)
articles = Article.objects.filter(category=category, status='published')
context = {
'category': category,
'articles': articles,
}
return render(request, 'blog/category_detail.html', context)
def year_archive(request, year):
"""年度归档"""
articles = Article.objects.filter(
publish_date__year=year,
status='published'
)
context = {
'year': year,
'articles': articles,
}
return render(request, 'blog/year_archive.html', context)
def month_archive(request, year, month):
"""月度归档"""
articles = Article.objects.filter(
publish_date__year=year,
publish_date__month=month,
status='published'
)
context = {
'year': year,
'month': month,
'articles': articles,
}
return render(request, 'blog/month_archive.html', context)
def author_detail(request, username):
"""作者详情页"""
author = get_object_or_404(User, username=username)
articles = Article.objects.filter(author=author, status='published')
context = {
'author': author,
'articles': articles,
}
return render(request, 'blog/author_detail.html', context)路径参数捕获
参数传递机制
URL中捕获的参数会作为关键字参数传递给视图函数:
python
# URL配置
path('article/<int:year>/<int:month>/<int:day>/<slug:slug>/',
views.article_detail, name='article_detail')
# 视图函数
def article_detail(request, year, month, day, slug):
"""文章详情视图"""
# year, month, day, slug 参数自动从URL中提取
article = get_object_or_404(
Article,
slug=slug,
publish_date__year=year,
publish_date__month=month,
publish_date__day=day,
status='published'
)
return render(request, 'blog/article_detail.html', {'article': article})参数验证和处理
python
# 复杂的URL参数处理
def article_detail(request, year, month, day, slug):
"""带参数验证的文章详情视图"""
# 验证日期参数
try:
from datetime import date
target_date = date(year, month, day)
except ValueError:
raise Http404("无效的日期")
# 查询文章
article = get_object_or_404(
Article,
slug=slug,
publish_date__date=target_date,
status='published'
)
context = {
'article': article,
'archive_date': target_date,
}
return render(request, 'blog/article_detail.html', context)可选参数
python
# 使用默认值处理可选参数
path('articles/', views.article_list, name='article_list'),
path('articles/page/<int:page>/', views.article_list, name='article_list_page'),
def article_list(request, page=1):
"""文章列表视图,支持分页"""
from django.core.paginator import Paginator
articles = Article.objects.filter(status='published')
paginator = Paginator(articles, 10) # 每页10篇
try:
articles = paginator.page(page)
except:
articles = paginator.page(1)
return render(request, 'blog/article_list.html', {'articles': articles})正则表达式路径
对于复杂的URL模式,Django还支持使用正则表达式:
python
from django.urls import re_path
from . import views
urlpatterns = [
# 使用命名组
re_path(r'^article/(?P<year>[0-9]{4})/$', views.year_archive, name='year_archive'),
re_path(r'^article/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$',
views.month_archive, name='month_archive'),
# 复杂模式匹配
re_path(r'^api/v(?P<version>[12])/article/(?P<id>[0-9]+)/$',
views.api_article_detail, name='api_article_detail'),
# 文件下载
re_path(r'^download/(?P<filename>[\w\-_\.]+\.(pdf|doc|txt))/$',
views.download_file, name='download_file'),
]正则表达式示例
python
# 更复杂的正则表达式URL模式
urlpatterns = [
# 匹配邮箱格式的用户名
re_path(r'^user/(?P<email>[\w\.-]+@[\w\.-]+\.\w+)/$',
views.user_by_email, name='user_by_email'),
# 匹配特定格式的产品代码
re_path(r'^product/(?P<code>[A-Z]{2}-\d{4}-[A-Z])/$',
views.product_detail, name='product_detail'),
# 匹配多级分类路径
re_path(r'^category/(?P<path>.+)/$',
views.nested_category, name='nested_category'),
]
def user_by_email(request, email):
"""通过邮箱查找用户"""
user = get_object_or_404(User, email=email)
return render(request, 'users/profile.html', {'user': user})
def product_detail(request, code):
"""产品详情页"""
# code格式:AA-1234-B
product = get_object_or_404(Product, code=code)
return render(request, 'products/detail.html', {'product': product})
def nested_category(request, path):
"""处理多级分类路径"""
# path可能是:tech/web/django 或 lifestyle/travel
categories = path.split('/')
# 处理嵌套分类逻辑
return render(request, 'blog/nested_category.html', {'path': path})URL配置最佳实践
1. 语义化URL设计
python
# 好的URL设计
urlpatterns = [
path('', views.home, name='home'),
path('articles/', views.article_list, name='article_list'),
path('articles/<slug:slug>/', views.article_detail, name='article_detail'),
path('categories/<slug:slug>/', views.category_detail, name='category_detail'),
path('authors/<str:username>/', views.author_profile, name='author_profile'),
path('archive/<int:year>/<int:month>/', views.monthly_archive, name='monthly_archive'),
]
# 避免的URL设计
urlpatterns = [
path('p/<int:id>/', views.post_detail), # 不清晰的路径
path('cat/<int:id>/', views.category_detail), # 缩写不明确
path('u/<int:id>/', views.user_profile), # 使用ID而非用户名
path('page.php?id=<int:id>', views.detail), # 模仿其他技术的URL
]2. URL层次结构
python
# 清晰的层次结构
urlpatterns = [
# 博客相关
path('blog/', include([
path('', views.blog_home, name='blog_home'),
path('posts/', views.post_list, name='post_list'),
path('posts/<slug:slug>/', views.post_detail, name='post_detail'),
path('categories/', views.category_list, name='category_list'),
path('categories/<slug:slug>/', views.category_detail, name='category_detail'),
])),
# 用户相关
path('users/', include([
path('', views.user_list, name='user_list'),
path('<str:username>/', views.user_profile, name='user_profile'),
path('<str:username>/posts/', views.user_posts, name='user_posts'),
])),
]3. 参数验证
python
# 自定义路径转换器
class YearConverter:
regex = r'[0-9]{4}'
def to_python(self, value):
return int(value)
def to_url(self, value):
return str(value)
# 注册转换器
from django.urls import register_converter
register_converter(YearConverter, 'year')
# 使用自定义转换器
urlpatterns = [
path('archive/<year:year>/', views.year_archive, name='year_archive'),
]4. 错误处理
python
def article_detail(request, slug):
"""文章详情视图,带错误处理"""
try:
article = Article.objects.get(slug=slug, status='published')
except Article.DoesNotExist:
# 记录访问日志
import logging
logger = logging.getLogger(__name__)
logger.warning(f'Article not found: {slug}')
# 返回404页面
raise Http404("文章不存在")
except Article.MultipleObjectsReturned:
# 处理重复slug的情况
article = Article.objects.filter(slug=slug, status='published').first()
return render(request, 'blog/article_detail.html', {'article': article})调试URL配置
1. URL调试技巧
python
# 在开发环境中添加调试URL
from django.conf import settings
if settings.DEBUG:
# 显示所有URL模式
def show_urls(request):
from django.urls import get_resolver
resolver = get_resolver()
url_patterns = []
def collect_urls(patterns, prefix=''):
for pattern in patterns:
if hasattr(pattern, 'pattern'):
url_patterns.append(prefix + str(pattern.pattern))
elif hasattr(pattern, 'url_patterns'):
collect_urls(pattern.url_patterns, prefix + str(pattern.pattern))
collect_urls(resolver.url_patterns)
return render(request, 'debug/urls.html', {'urls': url_patterns})
urlpatterns += [
path('debug/urls/', show_urls, name='debug_urls'),
]2. URL测试
python
# tests.py
from django.test import TestCase
from django.urls import reverse, resolve
from django.contrib.auth.models import User
from .models import Article
from . import views
class URLTests(TestCase):
def setUp(self):
self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
self.article = Article.objects.create(
title='Test Article',
slug='test-article',
content='Test content',
author=self.user,
status='published'
)
def test_home_url(self):
"""测试首页URL"""
url = reverse('blog:home')
self.assertEqual(url, '/')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_article_detail_url(self):
"""测试文章详情URL"""
url = reverse('blog:article_detail', kwargs={'slug': 'test-article'})
self.assertEqual(url, '/articles/test-article/')
# 测试URL解析
resolver = resolve(url)
self.assertEqual(resolver.func, views.article_detail)
self.assertEqual(resolver.kwargs, {'slug': 'test-article'})
def test_invalid_urls(self):
"""测试无效URL返回404"""
response = self.client.get('/articles/nonexistent/')
self.assertEqual(response.status_code, 404)小结
URL配置是Django应用的入口点,合理的URL设计能够:
- ✅ 提供清晰的URL结构
- ✅ 支持SEO优化
- ✅ 便于用户记忆和分享
- ✅ 方便开发和维护
关键要点:
- 使用语义化的URL路径
- 合理使用路径参数
- 保持URL结构的一致性
- 添加适当的错误处理
- 为URL模式命名以便反向解析
下一篇
我们将学习URL的高级特性,包括命名URL、反向解析和URL命名空间。