Skip to content

第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设计能够:

  1. ✅ 提供清晰的URL结构
  2. ✅ 支持SEO优化
  3. ✅ 便于用户记忆和分享
  4. ✅ 方便开发和维护

关键要点:

  • 使用语义化的URL路径
  • 合理使用路径参数
  • 保持URL结构的一致性
  • 添加适当的错误处理
  • 为URL模式命名以便反向解析

下一篇

我们将学习URL的高级特性,包括命名URL、反向解析和URL命名空间。

3.2 Django URL高级配置 →

目录

返回课程目录

Released under the Apache 2.0 License.