The Django Book

第八章 高级视图和URL配置

第三章,我们讲到DJANGO基本的视图功能和URL配置,这一章将涉及更多细节和高级功能。

URLconf 技巧

psiOnM <a href=”http://nbrzcxnliggw.com/“>nbrzcxnliggw</a>, [url=http://nbeqcslpbjht.com/]nbeqcslpbjht[/url], [link=http://xmxroewwggzy.com/]xmxroewwggzy[/link], http://nabarnvmapgz.com/

Yfphr6 <a href=”http://wkchavkykyhr.com/“>wkchavkykyhr</a>, [url=http://muokhwblpfjl.com/]muokhwblpfjl[/url], [link=http://ploxqieojkeb.com/]ploxqieojkeb[/link], http://xhutfvdzimfx.com/

看下这个 URLconf,它是建立在第三章的例子上:

from django.conf.urls.defaults import *
from mysite.views import current_datetime, hours_ahead, hours_behind, now_in_chicago, now_in_london

urlpatterns = patterns('',
    (r'^now/$', current_datetime),
    (r'^now/plus(\d{1,2})hours/$', hours_ahead),
    (r'^now/minus(\d{1,2})hours/$', hours_behind),
    (r'^now/in_chicago/$', now_in_chicago),
    (r'^now/in_london/$', now_in_london),
)

正如第三章中所解释的,在 URLconf 中的每一个入口包括了它所联系的视图函数,直接传入了一个函数对象。这就意味着需要在模块开始处导入视图函数。

但随着 Django 应用变得复杂,它的 URLconf 也在增长,并且维护这些导入可能使得管理变麻烦。(对每个新的view函数,你不得不记住要导入它,并且如果采用这种方法导入语句将变得相当长。)有可能通过导入 views 模块本身来避免这个麻烦。这个 URLconf 示例同上一个是等价的:

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^now/$', views.current_datetime),
    (r'^now/plus(\d{1,2})hours/$', views.hours_ahead),
    (r'^now/minus(\d{1,2})hours/$', views.hours_behind),
    (r'^now/in_chicago/$', views.now_in_chicago),
    (r'^now/in_london/$', views.now_in_london),
)

Django 还提供了另一种方法可以在 URLconf 中为某个特别的模式指定视图函数:你可以传入一个包含模块名和函数名的字符串,而不是函数对象本身。继续示例:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^now/$', 'mysite.views.current_datetime'),
    (r'^now/plus(\d{1,2})hours/$', 'mysite.views.hours_ahead'),
    (r'^now/minus(\d{1,2})hours/$', 'mysite.views.hours_behind'),
    (r'^now/in_chicago/$', 'mysite.views.now_in_chicago'),
    (r'^now/in_london/$', 'mysite.views.now_in_london'),
)

(注意视图名前后的引号。应该使用带引号的 'mysite.views.current_datetime' 而不是 mysite.views.current_datetime 。)

使用这个技术,就不必导入视图函数了;Django 会在第一次需要它时导入合适的视图函数,根据字符串所描述的视图函数的名字和路径。

当使用字符串技术时,你可以采用更简化的方式:提取出一个公共视图前缀。在我们的 URLconf 例子中,每一个视图字符串都是以 'mysite.views' 开始的,造成过多的输入。我们可以提取出公共前缀然后把它作为第一个参数传给 patterns() ,如:

from django.conf.urls.defaults import *

urlpatterns = patterns('mysite.views',
    (r'^now/$', 'current_datetime'),
    (r'^now/plus(\d{1,2})hours/$', 'hours_ahead'),
    (r'^now/minus(\d{1,2})hours/$', 'hours_behind'),
    (r'^now/in_chicago/$', 'now_in_chicago'),
    (r'^now/in_london/$', 'now_in_london'),
)

注意既不要在前缀后面跟着一个点号("." ),也不要在视图字符串前面放一个点号。 Django 会自动处理它们。

牢记这两种方法,哪种更好一些呢?这取决于你的个人编码习惯和需要。

UNF5vX <a href=”http://hdugwlkorxzw.com/“>hdugwlkorxzw</a>, [url=http://sbppygxqjdcj.com/]sbppygxqjdcj[/url], [link=http://icxfehixtpuo.com/]icxfehixtpuo[/link], http://tmihfbbxxydh.com/

N9gqns <a href=”http://wteejridtwke.com/“>wteejridtwke</a>, [url=http://lxotonbmgclb.com/]lxotonbmgclb[/url], [link=http://hkhsettjexoq.com/]hkhsettjexoq[/link], http://dlapxtivyork.com/

  • 如果你的视图函数存在于几个不同的 Python 模块的话,它可以使得 URLconf 更易读和管理。

WdAon8 <a href=”http://gnuhdlcakrdi.com/“>gnuhdlcakrdi</a>, [url=http://mkbdhjkqvlqt.com/]mkbdhjkqvlqt[/url], [link=http://rdpcyyxxithz.com/]rdpcyyxxithz[/link], http://rsbyearxigne.com/

  • 更容易对视图函数进行包装(wrap)。参见本章后面的《包装视图函数》一节。

  • 更 Pythonic,更符合 Python 的传统,如把函数当成对象传递。

两个方法都是有效的,甚至你可以在同一个 URLconf 中混用它们。决定权在你。

使用多个视图前缀

在实践中,如果你使用字符串技术,特别是当你的 URLconf 中没有一个公共前缀时,你最终可能混合视图。然而,你仍然可以利用视图前缀的简便方式来减少重复。只要增加多个 patterns() 对象,象这样:

旧的:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^/?$', 'mysite.views.archive_index'),
    (r'^(\d{4})/([a-z]{3})/$', 'mysite.views.archive_month'),
    (r'^tag/(\w+)/$', 'weblog.views.tag'),
)

新的:

from django.conf.urls.defaults import *

urlpatterns = patterns('mysite.views',
    (r'^/?$', 'archive_index'),
    (r'^(\d{4})/([a-z]{3})/$', 'archive_month'),
)

urlpatterns += patterns('weblog.views',
    (r'^tag/(\w+)/$', 'tag'),
)

整个框架关注的是存在一个名为 urlpatterns 的模块级别的变量。这个变量可以动态构建,正如本例中我们所做的一样。

调试模式中的特例

当谈到动态构建 urlpatterns 时,你可能想利用这一技术,在 Django 的调试模式时,来修改 URLconf 的行为。为了做到这一点,只要在运行时检查 DEBUG 配置项的值即可,如:

from django.conf.urls.defaults import*
from django.conf import settings

urlpatterns = patterns('',
    (r'^$', 'mysite.views.homepage'),
    (r'^(\d{4})/([a-z]{3})/$', 'mysite.views.archive_month'),
)

if settings.DEBUG:
    urlpatterns += patterns('',
        (r'^debuginfo$', 'mysite.views.debug'),
    )

在这个例子中,URL /debuginfo/ 将只有在你的 DEBUG 配置项设为 True 时才有效。

使用命名组

到目前为止,在所有 URLconf 例子中,我们使用的很简单,即 无命名 正则表达式组,在我们想要捕获的URL部分上加上小括号,Django 会将捕获的文本作为位置参数传递给视图函数。在更高级的用法中,还可以使用 命名 正则表达式组来捕获URL,并且将其作为 关键字 参数传给视图。

关键字参数 对比 位置参数

一个 Python 函数可以使用关键字参数或位置参数来调用,在某些情况下,可以同时进行使用。在关键字参数调用中,你要指定参数的名字和传入的值。在位置参数调用中,你只需传入参数,不需要明确指明哪个参数与哪个值对应,它们的对应关系隐含在参数的顺序中。

fozlR6 <a href=”http://alwpeqonzzpd.com/“>alwpeqonzzpd</a>, [url=http://eoblepdgxmzt.com/]eoblepdgxmzt[/url], [link=http://azkrzziaumle.com/]azkrzziaumle[/link], http://gfkmondejkch.com/

def sell(item, price, quantity):
    print "Selling %s unit(s) of %s at %s" % (quantity, item, price)

为了使用位置参数来调用它,你要按照在函数定义中的顺序来指定参数。

sell('Socks', '$2.50', 6)

为了使用关键字参数来调用它,你要指定参数名和值。下面的语句是等价的:

sell(item='Socks', price='$2.50', quantity=6)
sell(item='Socks', quantity=6, price='$2.50')
sell(price='$2.50', item='Socks', quantity=6)
sell(price='$2.50', quantity=6, item='Socks')
sell(quantity=6, item='Socks', price='$2.50')
sell(quantity=6, price='$2.50', item='Socks')

最后,你可以混合关键字和位置参数,只要所有的位置参数列在关键字参数之前。下面的语句与前面的例子是等价:

sell('Socks', '$2.50', quantity=6)
sell('Socks', price='$2.50', quantity=6)
sell('Socks', quantity=6, price='$2.50')

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

下面是一个使用无名组的 URLconf 的例子:

from django.conf.urls.defaults import *
from mysite import views

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

ZNpmIL <a href=”http://qucosvxmznkl.com/“>qucosvxmznkl</a>, [url=http://yjdxzjajhrsz.com/]yjdxzjajhrsz[/url], [link=http://zreyikrpzidz.com/]zreyikrpzidz[/link], http://esdpsbeowseu.com/

from django.conf.urls.defaults import *
from mysite import views

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

这段代码和前面的功能完全一样,只有一个细微的差别:把提取的值用命名参数的方式 传递给视图函数,而不是用按顺序的匿名参数的方式。

1tKNw5 <a href=”http://sdstpbzgmmbn.com/“>sdstpbzgmmbn</a>, [url=http://cvnjgcutyrcm.com/]cvnjgcutyrcm[/url], [link=http://reeabkflkcju.com/]reeabkflkcju[/link], http://bsppctebirpy.com/

month_archive(request, '2006', '03')

而带命名组,同样的请求就是这样的函数调用:

month_archive(request, year='2006', month='03')

使用命名组可以让你的URLconfs更加清晰,减少参数次序可能搞混的潜在BUG,还可以 让你在函数定义中对参数重新排序。接着上面这个例子,如果我们想修改URL把月份放到 年份的 前面 ,而不使用命名组的话,我们就不得不去修改视图 month_archive 的参数次序。如果我们使用命名组的话,修改URL里提取参数的次序对视图没有影响。

当然,命名组的代价就是失去了简洁性:一些开发者觉得命名组的语法丑陋和显得冗余。 命名组的另一个好处就是可读性强,特别是熟悉正则表达式或自己开发的Django 应用的开发者。看一眼URLconf里的这些命名组就知道这是干什么用的了。

理解匹配/分组算法

BsL55I <a href=”http://tasqioamrqly.com/“>tasqioamrqly</a>, [url=http://sftafnnqcumd.com/]sftafnnqcumd[/url], [link=http://ozgzxececcmx.com/]ozgzxececcmx[/link], http://uyetccdkpzdx.com/

  • 如果有任何命名的组,Django会忽略非命名组而直接使用命名组。

  • 否则,Django会把所有非命名组以位置参数的形式传递。

4CzvPf <a href=”http://bejuaosszhqg.com/“>bejuaosszhqg</a>, [url=http://mustddehuwps.com/]mustddehuwps[/url], [link=http://ogxgzaaxzdxl.com/]ogxgzaaxzdxl[/link], http://freqjzbnxehd.com/

3eZTaA <a href=”http://nmizqdrskmhq.com/“>nmizqdrskmhq</a>, [url=http://nsvgonwjlhyi.com/]nsvgonwjlhyi[/url], [link=http://osnzcwpdayxd.com/]osnzcwpdayxd[/link], http://posghywqmixc.com/

有时你会发现你写的视图函数是十分类似的,只有一点点的不同。比如说,你有两个视图,它们的内容是一致的,除了它们所用的模板不太一样:

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^foo/$', views.foo_view),
    (r'^bar/$', views.bar_view),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import MyModel

def foo_view(request):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response('template1.html', {'m_list': m_list})

def bar_view(request):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response('template2.html', {'m_list': m_list})

我们在这代码里面做了重复的工作,不够简练。起初你可能会想,通过对两个URL都试用同样的视图,在URL中使用括号捕捉请求,然后在视图中检查并决定使用哪个模板来去除代码的冗余,就像这样:

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^(foo)/$', views.foobar_view),
    (r'^(bar)/$', views.foobar_view),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import MyModel

def foobar_view(request, url):
    m_list = MyModel.objects.filter(is_new=True)
    if url == 'foo':
        template_name = 'template1.html'
    elif url == 'bar':
        template_name = 'template2.html'
    return render_to_response(template_name, {'m_list': m_list})

这种解决方案的问题还是老缺点,就是把你的URL耦合进你的代码里面了。如果你打算把 /foo/ 改成 /fooey/ 的话,那么你就得记住要去改变视图里面的代码。

优雅的解决方法:使用一个额外的URLconf参数。一个URLconf里面的每一个模式可以包含第三个数据:一个传到视图函数中的关键字参数的字典。

有了这个概念以后,我们就可以把我们现在的例子改写成这样:

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^foo/$', views.foobar_view, {'template_name': 'template1.html'}),
    (r'^bar/$', views.foobar_view, {'template_name': 'template2.html'}),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import MyModel

def foobar_view(request, template_name):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response(template_name, {'m_list': m_list})

如你所见,这个例子中,URLconf指定了 template_name 。而视图函数则会把它处理成另一个参数而已。

这额外的URLconf参数的技术以最少的麻烦给你提供了向视图函数传递额外信息的一个好方法。正因如此,这技术已被很多Django的捆绑应用使用,其中以我们将会在第9章讨论的通用视图系统最为明显。

下面的几节里面有一些关于你可以怎样把额外URLconf参数技术应用到你自己的工程的建议。

伪造捕捉到的URLconf值

比如说你有匹配某个模式的一堆视图,以及一个并不匹配这个模式的但它的视图逻辑是一样的URL。这种情况下,你可以伪造URL值的捕捉。这主要通过使用额外URLconf参数,使得这个多出来的URL使用同一个视图。

例如,你可能有一个显示某一个特定日子的某些数据的应用,URL类似这样的:

/mydata/jan/01/
/mydata/jan/02/
/mydata/jan/03/
# ...
/mydata/dec/30/
/mydata/dec/31/

这太简单了,你可以在一个URLconf中捕捉这些值,像这样(使用命名组的方法):

urlpatterns = patterns('',
    (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view),
)

然后视图函数的原型看起来会是:

def my_view(request, month, day):
    # ....

这种解决方案很直接,没有用到什么你没见过的技术。问题在于当你想为添加一个使用 my_view 视图的URL但它没有包含一个 month 和/或者 一个 day

比如你可能会想增加这样一个URL, /mydata/birthday/ , 这个URL等价于 /mydata/jan/06/ 。这时你可以这样利用额外URLconf参数:

urlpatterns = patterns('',
    (r'^mydata/birthday/$', views.my_view, {'month': 'jan', 'day': '06'}),
    (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view),
)

在这里最帅的地方莫过于你根本不用改变你的视图函数。视图函数只会关心它 获得monthday 参数,它不会去管这些参数到底是捕捉回来的还是被额外提供的。

创建一个通用视图

抽取出我们代码中共性的东西是一个很好的编程习惯。比如,像以下的两个Python函数:

def say_hello(person_name):
    print 'Hello, %s' % person_name

def say_goodbye(person_name):
    print 'Goodbye, %s' % person_name

我们可以把问候语提取出来变成一个参数:

def greet(person_name, greeting):
    print '%s, %s' % (greeting, person_name)

通过使用额外的URLconf参数,你可以把同样的思想应用到Django的视图中。

了解这个以后,你可以开始创作高抽象的视图。更具体地说,比如这个视图显示一系列的 Event 对象,那个视图显示一系列的 BlogEntry 对象,并意识到它们都是一个用来显示一系列对象的视图的特例,而对象的类型其实就是一个变量。

以这段代码作为例子:

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^events/$', views.event_list),
    (r'^blog/entries/$', views.entry_list),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import Event, BlogEntry

def event_list(request):
    obj_list = Event.objects.all()
    return render_to_response('mysite/event_list.html', {'event_list': obj_list})

def entry_list(request):
    obj_list = BlogEntry.objects.all()
    return render_to_response('mysite/blogentry_list.html', {'entry_list': obj_list})

这两个视图做的事情实质上是一样的:显示一系列的对象。让我们把它们显示的对象的类型抽象出来:

# urls.py

from django.conf.urls.defaults import *
from mysite import models, views

urlpatterns = patterns('',
    (r'^events/$', views.object_list, {'model': models.Event}),
    (r'^blog/entries/$', views.object_list, {'model': models.BlogEntry}),
)

# views.py

from django.shortcuts import render_to_response

def object_list(request, model):
    obj_list = model.objects.all()
    template_name = 'mysite/%s_list.html' % model.__name__.lower()
    return render_to_response(template_name, {'object_list': obj_list})

就这样小小的改动,我们突然发现我们有了一个可复用的,模型无关的视图!从现在开始,当我们需要一个视图来显示一系列的对象时,我们可以简简单单的重用这一个 object_list 视图,而无须另外写视图代码了。以下是我们做过的事情:

  • 我们通过 model 参数直接传递了模型类。额外URLconf参数的字典是可以传递任何类型的对象,而不仅仅只是字符串。

  • 这一行: model.objects.all()鸭子界定 (原文:duck typing,是计算机科学中一种动态类型判断的概念)的一个例子:如果一只鸟走起来像鸭子,叫起来像鸭子,那我们就可以把它当作是鸭子了。需要注意的是代码并不知道 model 对象的类型是什么;它只要求 model 有一个 objects 属性,而这个属性有一个 all() 方法。

  • 我们使用 model.__name__.lower() 来决定模板的名字。每个Python的类都有一个 __name__ 属性返回类名。这特性在当我们直到运行时刻才知道对象类型的这种情况下很有用。比如, BlogEntry 类的 __name__ 就是字符串 'BlogEntry'

  • 这个例子与前面的例子稍有不同,我们传递了一个通用的变量名给模板。当然我们可以轻易的把这个变量名改成 blogentry_list 或者 event_list ,不过我们打算把这当作练习留给读者。

因为数据库驱动的网站都有一些通用的模式,Django提供了一个通用视图的集合,使用它可以节省你的时间。我们将会在下一章讲讲Django的内置通用视图。

提供视图配置选项

如果你发布一个Django的应用,你的用户可能会希望配置上能有些自由度。这种情况下,为你认为用户可能希望改变的配置选项添加一些钩子到你的视图中会是一个很好的主意。你可以用额外URLconf参数实现。

一个应用中比较常见的可供配置代码是模板名字:

def my_view(request, template_name):
    var = do_something()
    return render_to_response(template_name, {'var': var})

了解捕捉值和额外参数之间的优先级

当冲突出现的时候,额外URLconf参数优先于捕捉值。也就是说,如果URLconf捕捉到的一个命名组变量和一个额外URLconf参数包含的变量同名时,额外URLconf参数的值会被使用。

例如,下面这个URLconf:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^mydata/(?P<id>\d+)/$', views.my_view, {'id': 3}),
)

这里,正则表达式和额外字典都包含了一个 id 。硬编码的(额外字典的) id 将优先使用。就是说任何请求(比如, /mydata/2/ 或者 /mydata/432432/ )都会作 id 设置为 3 对待,不管URL里面能捕捉到什么样的值。

聪明的读者会发现在这种情况下,在正则表达式里面写上捕捉是浪费时间的,因为 id 的值总是会被字典中的值覆盖。没错,我们说这个的目的只是为了让你不要犯这样的错误。

使用缺省视图参数

另外一个方便的特性是你可以给一个视图指定默认的参数。这样,当没有给这个参数赋值的时候将会使用默认的值。

请看例子:

# urls.py

from django.conf.urls.defaults import *

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

# views.py

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

在这里,两个URL表达式都指向了同一个视图 views.page ,但是第一个表达式没有传递任何参数。如果匹配到了第一个样式, page() 函数将会对参数 num 使用默认值 "1" ,如果第二个表达式匹配成功, page() 函数将使用正则表达式传递过来的num的值。

就像前面解释的一样,这种技术与配置选项的联用是很普遍的。以下这个例子比提供视图配置选项一节中的例子有些许的改进。它为 template_name 提供了一个默认值:

def my_view(request, template_name='mysite/my_view.html'):
    var = do_something()
    return render_to_response(template_name, {'var': var})

特殊情况下的视图

有时你有一个模式来处理在你的URLconf中的一系列URL,但是有时候需要特别处理其中的某个URL。在这种情况下,要使用将URLconf中把特殊情况放在首位的线性处理方式 。

例如,Django的admin站点中添加一个对象页面是如下配置的:

urlpatterns = patterns('',
    # ...
    ('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
    # ...
)

这将匹配像 /myblog/entries/add//auth/groups/add/ 这样的URL 。然而,对于用户对象的添加页面( /auth/user/add/ )是个特殊情况,因为它不会显示所有的表单域,它显示两个密码域等等。我们 可以 在视图中特别指出以解决这种情况:

def add_stage(request, app_label, model_name):
    if app_label == 'auth' and model_name == 'user':
        # do special-case code
    else:
        # do normal code

不过,就如我们多次在这章提到的,这样做并不优雅:因为它把URL逻辑放在了视图中。更优雅的解决方法是,我们要利用URLconf从顶向下的解析顺序这个特点:

urlpatterns = patterns('',
    # ...
    ('^auth/user/add/$', 'django.contrib.admin.views.auth.user_add_stage'),
    ('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
    # ...
)

在这种情况下,象 /auth/user/add/ 的请求将会被 user_add_stage 视图处理。尽管URL也匹配第二种模式,它会先匹配上面的模式。(这是短路逻辑。)

从URL中捕获文本

每个被捕获的参数将被作为纯Python字符串来发送,而不管正则表达式中的格式。举个例子,在这行URLConf中:

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

尽管 \d{4} 将只匹配整数的字符串,但是参数 year 是作为字符串传至 views.year_archive() 的,而不是整型。

当你在写视图代码时记住这点很重要,许多Python内建的方法对于接受的对象的类型很讲究。一个典型的的错误就是用字符串值而不是整数值来创建 datetime.date 对象:

>>> import datetime
>>> datetime.date('1993', '7', '9')
Traceback (most recent call last):
    ...
TypeError: an integer is required
>>> datetime.date(1993, 7, 9)
datetime.date(1993, 7, 9)

回到URLconf和视图处,错误看起来很可能是这样:

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^articles/(\d{4})/(\d{2})/(\d{2})/$', views.day_archive),
)

# views.py

import datetime

def day_archive(request, year, month, day)
    # The following statement raises a TypeError!
    date = datetime.date(year, month, day)

因此, day_archive() 应该这样写才是正确的:

def day_archive(request, year, month, day)
    date = datetime.date(int(year), int(month), int(day))

注意,当你传递了一个并不完全包含数字的字符串时, int() 会抛出 ValueError 的异常,不过我们已经避免了这个错误,因为在URLconf的正则表达式中已经确保只有包含数字的字符串才会传到这个视图函数中。

决定URLconf搜索的东西

当一个请求进来时,Django试着将请求的URL作为一个普通Python字符串进行URLconf模式匹配(而不是作为一个Unicode字符串)。这并不包括 GETPOST 参数或域名。它也不包括第一个斜杠,因为每个URL必定有一个斜杠。

例如,在向 http://www.example.com/myapp/ 的请求中,Django将试着去匹配 myapp/ 。在向 http://www.example.com/myapp/?page=3 的请求中,Django同样会去匹配 myapp/

在解析URLconf时,请求方法(例如, POSTGETHEAD )并 不会 被考虑。换而言之,对于相同的URL的所有请求方法将被导向到相同的函数中。因此根据请求方法来处理分支是视图函数的责任。

包含其他URLconf

如果你试图让你的代码用在多个基于Django的站点上,你应该考虑将你的URLconf以包含的方式来处理。

在任何时候,你的URLconf都可以包含其他URLconf模块。对于根目录是基于一系列URL的站点来说,这是必要的。例如下面的,URLconf包含了其他URLConf:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^weblog/', include('mysite.blog.urls')),
    (r'^photos/', include('mysite.photos.urls')),
    (r'^about/$', 'mysite.views.about'),
)

这里有个很重要的地方:例子中的指向 include() 的正则表达式并 包含一个 $ (字符串结尾匹配符),但是包含了一个斜杆。每当Django遇到 include() 时,它将截断匹配的URL,并把剩余的字符串发往包含的URLconf作进一步处理。

继续看这个例子,这里就是被包含的URLconf mysite.blog.urls

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^(\d\d\d\d)/$', 'mysite.blog.views.year_detail'),
    (r'^(\d\d\d\d)/(\d\d)/$', 'mysite.blog.views.month_detail'),
)

通过这两个URLconf,下面是一些处理请求的例子:

  • /weblog/2007/ :在第一个URLconf中,模式 r'^weblog/' 被匹配。因为它是一个 include() ,Django将截掉所有匹配的文本,在这里是 'weblog/' 。URL剩余的部分是 2007/ , 将在 mysite.blog.urls 这个URLconf的第一行中被匹配到。

  • /weblog//2007/ :在第一个URLconf中,模式 r'^weblog/' 被匹配。因为它是一个 include() ,Django将截掉所有匹配的文本,在这里是 'weblog/' 。URL剩余的部分是 /2007/ (开头有一个斜杠),将不会匹配 mysite.blog.urls 中的任何URLconf。

  • /about/ : 这个匹配第一个URLconf中的 mysite.views.about 视图。只是为了示范你可以混合 include() patterns和 non-include() patterns在一起使用。

捕获的参数如何和include()协同工作

一个被包含的URLconf接收任何来自parent URLconfs的被捕获的参数,比如:

# root urls.py

from django.conf.urls.defaults import *

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

# foo/urls/blog.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^$', 'foo.views.blog_index'),
    (r'^archive/$', 'foo.views.blog_archive'),
)

在这个例子中,被捕获的 username 变量将传递给被包含的 URLconf,进而传递给那个URLconf中的 每一个 视图函数。

注意,这个被捕获的参数 总是 传递到被包含的URLconf中的 每一 行,不管那些行对应的视图是否需要这些参数。因此,这个技术只有在你确实需要那个被传递的参数的时候才显得有用。

额外的URLconf如何和include()协同工作

相似的,你可以传递额外的URLconf选项到 include() , 就像你可以通过字典传递额外的URLconf选项到普通的视图。当你这样做的时候,被包含URLconf的 每一 行都会收到那些额外的参数。

比如,下面的两个URLconf在功能上是相等的。

第一个:

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^blog/', include('inner'), {'blogid': 3}),
)

# inner.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^archive/$', 'mysite.views.archive'),
    (r'^about/$', 'mysite.views.about'),
    (r'^rss/$', 'mysite.views.rss'),
)

第二个

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^blog/', include('inner')),
)

# inner.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^archive/$', 'mysite.views.archive', {'blogid': 3}),
    (r'^about/$', 'mysite.views.about', {'blogid': 3}),
    (r'^rss/$', 'mysite.views.rss', {'blogid': 3}),
)

这个例子和前面关于被捕获的参数一样(在上一节就解释过这一点),额外的选项将 总是 被传递到被包含的URLconf中的 每一 行,不管那一行对应的视图是否确实作为有效参数接收这些选项,因此,这个技术只有在你确实需要那个被传递的额外参数的时候才显得有用。

接下来?

Django的主要目标之一就是减少开发者的代码输入量,并且在这一章中我们建议如何减少你的视图和URLconf的代码量。

为了减少代码量,下一个合理步骤就是如何避免全部手工书写视图代码,这就是下一章的主题。

Copyright 2006 Adrian Holovaty and Jacob Kaplan-Moss.
This work is licensed under the GNU Free Document License.
Hosting graciously provided by media temple
Chinese translate hosting by py3k.cn.