URL 调度程序

作者:Django 团队
译者:weizhong2004@gmail.com
翻译开始日期:2006-03-31
翻译完成日期:2006-03-31
最后更新日期:2006-05-12
原文版本:2792

目录

对高质量的 WEB 应用程序来说, 有一个干净优雅的 URL 调度机制是至关重要的. Django 允许你根据需要设计 URLs ,没有任何框架层的限制.

不需要 .php.cgi 扩展名, 也没有象 0,2097,1-1-1928,00 那样的丑陋东西.

参阅 WWW 的缔造者 Tim Berners-Lee 所写的 Cool URIs don't change, 这篇文章的主题就是为什么 URL 应该是干净并且便于使用的.

1   概述

要为一个应用程序设计 URLs, 要生成一个称为 URLconf (URL 配置) 的非正式的 Python 模块. 该模块为纯 Python 代码, 它的作用就是在 URL 模式(简单的正则表达式)及 Python 回调函数( 你的 views) 之间建立映射.

这个映射根本需要可以很短,也可以很长. 它能够引用其它的映射. 由于是纯 Python 代码, 就有能力动态的创建这种映射.

2   Django 如何处理一个请求 (request)

当用户请求一个页面时,通过以下运算法则决定执行哪一段 Python代码:

  1. Django首先检查 settings file 中的 ROOT_URLCONF 设置. 这是一个表示 Python 导入 URLconf 需要的绝对路径字符串. 例如: "mydjangoapps.urls".
  2. Django 载入这个 Python 模块并寻找变量 urlpatterns. 该变量是一个 django.conf.urls.defaults.patterns() 函数返回值格式的元素组成的列表.
  3. Django 顺序处理每个 URL 模式, 直到找到一个与请求URL的相匹配的模式为止.
  4. 一旦匹配成功, Django 导入并调用对应的 view, 也就是相应的 Python 函数. 这个 view 函数会得到一个 request object 作为它的第一个参数, 在正则表达式匹配中得到的其它值也传递给 view 作为其它的参数.

3   例子

这里有一个 URLconf 的示例:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^articles/2003/$', 'news.views.special_case_2003'),
    (r'^articles/(\d{4})/$', 'news.views.year_archive'),
    (r'^articles/(\d{4})/(\d{2})/$', 'news.views.month_archive'),
    (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'news.views.article_detail'),
)

注意:

  • from django.conf.urls.defaults import * 使得 patterns() 函数可用.
  • 要从 URL 中捕获值, 用小括号将其括起来即可.
  • 不要添加一个前导反斜线, 因为每个 URL 都有. 举例来说,写成 ^articles, 而不要写成 ^/articles.
  • 每个正则表达式前面的 'r' 是可选的,不过我们推荐使用原始字符串来表达正则表达式.参阅 Dive Into Python's explanation.

请求示例:

  • 一个 /articles/2005/03/ 请求将匹配列表中的第三个入口. Django 自动调用函数 news.views.month_archive(request, '2005', '03').
  • /articles/2005/3/ 不匹配任何一个 URL 模式,因为第三个入口要求月份必须是两位数.
  • /articles/2003/ 将与列表的第一个入口匹配, 而不是第二个.因为模式是按顺序处理的,第一个入口匹配成功即停止处理. 这是很好的特性,你可以方便的添加一些特定项.
  • /articles/2003 不匹配任何模式, 因为每个模式都要求以一个反斜线结尾.
  • /articles/2003/03/3/ 匹配最后一个模式. Django 自动调用函数 news.views.article_detail(request, '2003', '03', '3').

4   命名分组

上面的例子使用了简单的, 非命名的 正则表达式分组 (使用小括号) 来从 URL 中捕获传递给 view 函数的 位置相关的 参数. 作为高级一点的用法,可以使用 命名 正则表达式分组来捕获传递给 view 函数的 关键字 参数.

在 Python 正则表达式里, 命名分组的语法是 (?P<name>pattern), 这里 name 是分组的名字而 pattern 是要匹配的模式.

下面用命名分组重写上面的例子:

urlpatterns = patterns('',
    (r'^articles/2003/$', 'news.views.special_case_2003'),
    (r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
    (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'news.views.month_archive'),
    (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d+)/$', 'news.views.article_detail'),
)

上面的代码与前面的代码功能完全相同, 不过传递给 view 函数的不再是位置相关参数,而变成了关键字参数.比如:

  • 页面请求 /articles/2005/03/ 会自动调用函数 news.views.month_archive(request, year='2005', month='03'), 而不是 news.views.month_archive(request, '2005', '03').
  • 页面请求 /articles/2003/03/3/ 会自动调用函数 news.views.article_detail(request, year='2003', month='03', day='3').

实际上, 这意味着 URLconfs 更清晰,而且避免了参数顺序错误引发的 bug -- 定义 view 函数时不必特别在意参数的顺序.当然有些开发人员认为命名分组的语法很丑并且繁琐.

4.1   匹配/分组算法

下面是 URLconf 解析遵循的算法:

只要存在命名参数, 就使用命名参数,非命名参数将被忽略.否则将所有非命名参数顺序传递给 view 函数.

不论哪种情况, 它都会传递一些额外的关键字参数给 view 函数.参阅下文中的 "传递额外参数给 view 函数" .

5   URLconf 搜索依据

URLconf 根据请求 URL (作为标准 Python 字符串处理) 进行搜索. 不包括任何 GET 或 POST 参数,也不包括域名.

比如,页面请求 http://www.example.com/myapp/, URLconf 将查找 /myapp/.

对页面请求 http://www.example.com/myapp/?page=3, URLconf 将查找 /myapp/.

URLconf 不考虑请求的方法. 也就是说,同一 URL 不论使用何种请求方法 -- POST, GET, HEAD, 等等. -- 都会导向同一个 view 函数.

6   urlpatterns 变量的语法

urlpatterns 是一个 Python 列表, 格式与函数 django.conf.urls.defaults.patterns() 的返回值相同. 每个人都应该使用 patterns() 来创建 urlpatterns 变量.

习惯在 URLconf 的顶部使用 from django.conf.urls.defaults import * 语句, 这将导入以下对象到 URLconf:

6.1   patterns

是一个函数, 它接受一个 前缀 参数及任意个 URL 模式, 返回一个满足 Django 要求格式的 URL 模式的列表.

patterns() 的第一个参数 prefix 必须是一个字符串. 参阅下文中的 "The view prefix" .

其它的参数应该是下面格式的 tuple:

(regular expression, Python callback function [, optional dictionary])

... 这里 optional dictionary 是可选的. (参阅下文中的 传递额外参数给 view 函数 )

6.2   handler404

是一个 view 函数的字符串表示. 若没有任何 URL 模式匹配, 调用该 view 函数.

默认值为: 'django.views.defaults.page_not_found'. 通常没有必要修改这个默认值.

6.3   handler500

是一个 view 函数的字符串表示. 当发生服务器错误时调用该 view 函数. 当 view 代码发生运行时代码时会发生服务器错误..

默认值为: 'django.views.defaults.server_error'. 通常没有必要修改这个默认值.

6.4   include

是一个函数, 它接受一个指向另一个 URLconf 的 python 路径作为参数. 该路径应该被 "included" 到当前位置.参阅下文中的 Including other URLconfs .

7   在 URLs 中捕获文本要注意的问题

每个捕获的参数都以 Python 字符串的形式被发送给 view 函数作参数, 而不管正则表达式的匹配类型. 举个例子,下面的 URLconf 行:

(r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),

... 传递给 news.views.year_archive()year 参数会是一个字符串,而不是一个整数,尽管 \d{4} 只匹配整数字符串.

一个小技巧是在 view 函数中为参数指定默认值. 下面是一个 URLconf 及 view 的例子:

# URLconf
urlpatterns = patterns('',
    (r'^blog/$', 'blog.views.page'),
    (r'^blog/page(?P<num>\d+)/$', 'blog.views.page'),
)

# View (in blog/views.py)
def page(request, num="1"):
    # Output the appropriate page of blog entries, according to num.

在上面的例子里, 所有的 URL 模式指向同一个 view 函数 -- blog.views.page -- 不过第一个模式并不从 URL 中捕获任何值. 在匹配第一个模式时, page() 函数使用 num 参数的默认值: "1". 如果匹配第二个模式, page() 将使用从正则表达式中捕获的 num 值.

8   性能

一个 urlpatterns 中的每一个正则表达式都在其第一次被访问时编译, 这使得 Django URL 调度起来象闪电一样快!

9   view 前缀

你可在 patterns() 调用时指定一个通用的 view 前缀, 以减少代码重复.

下面是来自 Django overview 的一个 URLconf 示例:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^articles/(\d{4})/$', 'myproject.news.views.year_archive'),
    (r'^articles/(\d{4})/(\d{2})/$', 'myproject.news.views.month_archive'),
    (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'myproject.news.views.article_detail'),
)

在这个例子里,每个 view 都有同样的前缀 -- 'myproject.news.views'.你可以利用 patterns() 函数的第一个参数来指定一个 view 前缀, 该前缀将应用在每一个 view 函数上.

下面重写上面的例子,两者对比一下,后者简明的多:

from django.conf.urls.defaults import *

urlpatterns = patterns('myproject.news.views',
    (r'^articles/(\d{4})/$', 'year_archive'),
    (r'^articles/(\d{4})/(\d{2})/$', 'month_archive'),
    (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'article_detail'),
)

注意不要放一个句点给前缀结尾 ("."). Django 能自动处理这一切.

10   包括其它的 URLconfs

在任意位置, urlpatterns 可以 "include" 其它的 URLconf 模块. This essentially "roots" a set of URLs below other ones.

举例来说, 下面是 Django website 的 URLconf. 它包括了一系列其它 URLconfs:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^weblog/',        include('django_website.apps.blog.urls.blog')),
    (r'^documentation/', include('django_website.apps.docs.urls.docs')),
    (r'^comments/',      include('django.contrib.comments.urls.comments')),
)

注意上例中的正则表达式以一个反斜线而不是 $ 字符(表示字符串结束)结尾.只要 Django 遇到 include(), 它就在该点砍掉任何匹配的 URL 部分,并将剩余的部分发送到 URLconf 以进行下一步处理.

10.1   捕获参数

被包含的 URLconf 自动接收任何父 URLconfs 捕获的所有参数, 因此下面的例子是合法的:

# In settings/urls/main.py
urlpatterns = patterns('',
    (r'^(?P<username>\w+)/blog/', include('foo.urls.blog')),
)

# In foo/urls/blog.py
urlpatterns = patterns('foo.views',
    (r'^$', 'blog.index'),
    (r'^archive/$', 'blog.archive'),
)

在上面的例子里, 被捕获的 "username" 变量被传递给被包括的 URLconf, 就象预期的一样.

11   传递额外参数给 view 函数

URLconfs 允许以 Python 字典的形式传递额外的参数给 view 函数.

任何 URLconf tuple 都可以有可选的第三个元素, 一个传递给 view 函数的额外的字典参数.

示例:

urlpatterns = patterns('blog.views',
    (r'^/blog/(?P<year>\d{4})/$', 'year_archive', {'foo': 'bar'}),
)

在这个例子里, 页面请求 /blog/2005/, Django 会自动调用 blog.views.year_archive() 函数, 并传递给它以下关键字参数:

year='2005', foo='bar'

该技术在 generic viewssyndication framework 中得到了广泛应用, 用来传递元数据及选项给 view 函数.