============== URL 调度程序 ============== :作者: Django 团队 :译者: weizhong2004@gmail.com :翻译开始日期: 2006-03-31 :翻译完成日期: 2006-03-31 :最后更新日期: 2006-05-12 :原文版本: 2792 .. contents:: 目录 .. sectnum:: 对高质量的 WEB 应用程序来说, 有一个干净优雅的 URL 调度机制是至关重要的. Django 允许你根据需要设计 URLs ,没有任何框架层的限制. 不需要 ``.php`` 或 ``.cgi`` 扩展名, 也没有象 ``0,2097,1-1-1928,00`` 那样的丑陋东西. 参阅 WWW 的缔造者 Tim Berners-Lee 所写的 `Cool URIs don't change`_, 这篇文章的主题就是为什么 URL 应该是干净并且便于使用的. .. _Cool URIs don't change: http://www.w3.org/Provider/Style/URI 概述 ======== 要为一个应用程序设计 URLs, 要生成一个称为 **URLconf** (URL 配置) 的非正式的 Python 模块. 该模块为纯 Python 代码, 它的作用就是在 URL 模式(简单的正则表达式)及 Python 回调函数( 你的 views) 之间建立映射. 这个映射根本需要可以很短,也可以很长. 它能够引用其它的映射. 由于是纯 Python 代码, 就有能力动态的创建这种映射. 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 作为其它的参数. .. _settings file: http://www.djangoproject.com/documentation/settings/ .. _request object: http://www.djangoproject.com/documentation/request_response/#httprequest-objects 例子 ======= 这里有一个 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')``. .. _Dive Into Python's explanation: http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3 命名分组 ============ 上面的例子使用了简单的, *非命名的* 正则表达式分组 (使用小括号) 来从 URL 中捕获传递给 view 函数的 *位置相关的* 参数. 作为高级一点的用法,可以使用 *命名* 正则表达式分组来捕获传递给 view 函数的 *关键字* 参数. 在 Python 正则表达式里, 命名分组的语法是 ``(?Ppattern)``, 这里 ``name`` 是分组的名字而 ``pattern`` 是要匹配的模式. 下面用命名分组重写上面的例子:: urlpatterns = patterns('', (r'^articles/2003/$', 'news.views.special_case_2003'), (r'^articles/(?P\d{4})/$', 'news.views.year_archive'), (r'^articles/(?P\d{4})/(?P\d{2})/$', 'news.views.month_archive'), (r'^articles/(?P\d{4})/(?P\d{2})/(?P\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 函数时不必特别在意参数的顺序.当然有些开发人员认为命名分组的语法很丑并且繁琐. 匹配/分组算法 ------------------------------- 下面是 URLconf 解析遵循的算法: 只要存在命名参数, 就使用命名参数,非命名参数将被忽略.否则将所有非命名参数顺序传递给 view 函数. 不论哪种情况, 它都会传递一些额外的关键字参数给 view 函数.参阅下文中的 "传递额外参数给 view 函数" . 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 函数. urlpatterns 变量的语法 ================================== ``urlpatterns`` 是一个 Python 列表, 格式与函数 ``django.conf.urls.defaults.patterns()`` 的返回值相同. 每个人都应该使用 ``patterns()`` 来创建 ``urlpatterns`` 变量. 习惯在 URLconf 的顶部使用 ``from django.conf.urls.defaults import *`` 语句, 这将导入以下对象到 URLconf: patterns -------- 是一个函数, 它接受一个 前缀 参数及任意个 URL 模式, 返回一个满足 Django 要求格式的 URL 模式的列表. ``patterns()`` 的第一个参数 ``prefix`` 必须是一个字符串. 参阅下文中的 "The view prefix" . 其它的参数应该是下面格式的 tuple:: (regular expression, Python callback function [, optional dictionary]) ... 这里 ``optional dictionary`` 是可选的. (参阅下文中的 _`传递额外参数给 view 函数` ) handler404 ---------- 是一个 view 函数的字符串表示. 若没有任何 URL 模式匹配, 调用该 view 函数. 默认值为: ``'django.views.defaults.page_not_found'``. 通常没有必要修改这个默认值. handler500 ---------- 是一个 view 函数的字符串表示. 当发生服务器错误时调用该 view 函数. 当 view 代码发生运行时代码时会发生服务器错误.. 默认值为: ``'django.views.defaults.server_error'``. 通常没有必要修改这个默认值. include ------- 是一个函数, 它接受一个指向另一个 URLconf 的 python 路径作为参数. 该路径应该被 "included" 到当前位置.参阅下文中的 _`Including other URLconfs` . 在 URLs 中捕获文本要注意的问题 =============================== 每个捕获的参数都以 Python 字符串的形式被发送给 view 函数作参数, 而不管正则表达式的匹配类型. 举个例子,下面的 URLconf 行:: (r'^articles/(?P\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\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`` 值. 性能 =========== 一个 ``urlpatterns`` 中的每一个正则表达式都在其第一次被访问时编译, 这使得 Django URL 调度起来象闪电一样快! 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 能自动处理这一切. .. _Django overview: http://www.djangoproject.com/documentation/overview/ 包括其它的 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 以进行下一步处理. .. _`Django website`: http://www.djangoproject.com/ 捕获参数 ------------------- 被包含的 URLconf 自动接收任何父 URLconfs 捕获的所有参数, 因此下面的例子是合法的:: # In settings/urls/main.py urlpatterns = patterns('', (r'^(?P\w+)/blog/', include('foo.urls.blog')), ) # In foo/urls/blog.py urlpatterns = patterns('foo.views', (r'^$', 'blog.index'), (r'^archive/$', 'blog.archive'), ) 在上面的例子里, 被捕获的 ``"username"`` 变量被传递给被包括的 URLconf, 就象预期的一样. 传递额外参数给 view 函数 ======================================= URLconfs 允许以 Python 字典的形式传递额外的参数给 view 函数. 任何 URLconf tuple 都可以有可选的第三个元素, 一个传递给 view 函数的额外的字典参数. 示例:: urlpatterns = patterns('blog.views', (r'^/blog/(?P\d{4})/$', 'year_archive', {'foo': 'bar'}), ) 在这个例子里, 页面请求 ``/blog/2005/``, Django 会自动调用 ``blog.views.year_archive()`` 函数, 并传递给它以下关键字参数:: year='2005', foo='bar' 该技术在 `generic views`_ 及 `syndication framework`_ 中得到了广泛应用, 用来传递元数据及选项给 view 函数. .. _generic views: http://www.djangoproject.com/documentation/generic_views/ .. _syndication framework: http://www.djangoproject.com/documentation/syndication/