编写你的第一个Django程序,第三部分(下)
抛出404异常
现在来看看这个视图——该页面会显示指定ID对应投票的问题。下面是全部代码:
from django.http import Http404
# ...
def detail(request, poll_id):
try:
p = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404
return render_to_response('polls/detail.html', {'poll': p})
这里有个新的概念:如果根据ID找不到对应的数据,视图函数就会抛出一个Http404异常。
快捷方法:get_object_or_404()
使用get()函数并判断是否抛出Http404错误是一套连贯的操作,因此Django提供了快捷方法。下面是重写过的detail()函数。
from django.shortcuts import render_to_response, get_object_or_404
# ...
def detail(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/detail.html', {'poll': p})
get_object_or_404()函数接收一个Django模型作为第一参数,其他的关键字参数将传入到模型对象的get()方法中。如果查询不到任何结果时,将抛出http404异常。
哲学
为什么要用get_object_or_404()而不在更高级别上自动捕获ObjectDoesNotExist异常呢?为什么使用模型API来抛出http404异常而不是抛出ObjectDoesNotExist异常?
还有一个与get_object_or_404()方法一样的get_list_or_404()方法——只是对应的模型对象调用的是filter()不是get()方法。如果返回列表为空的话就会抛出Http404异常。
编写404(找不到页面)视图
当视图里抛出一个Http404异常时,Django会加载一个专门用来处理这个异常的视图函数。
Django会根据变量handler404来查找这个视图,这个变量也是个Python包格式的字符串——和URLconf里面的回调函数的格式是一样的。404视图没有任何特别之处,它就只是个普通的视图而已。
你不用太关注于怎样编写404视图。一般,URLconf设置里有下面的机制:
from django.conf.urls.defaults import *
这会将handler404导入到当前模块中。你可以在django/conf/urls/defaults.py中看到,handler404默认设置成了django.views.defaults.page_not_found()这个方法。
关于404视图还有三点要注意:
l 当Django没有在URLconf设置中找到能够匹配当前URL的正则式时,也会调用404视图函数。
l 如果你没有自定义404视图——一般情况下会使用默认的——你还是要在模板目录下创建一个404.html文件。默认的404视图会为所有的404异常使用这个模板。
l 如果DEBUG设置为True(在settings模块里),404视图是永远都不会启用的,取而代之的是显示出追踪错误信息。
编写500视图(服务器错误)
类似于404错误,URLconf也可以定义一个handler500方法,在发生服务器错误时,这个方法会调用一个指定的视图。服务器错误是指在视图代码中产生的运行时错误。
使用模板
回来看看detail()视图函数。在给定了context变量——poll之后,现在的模板polls/detail.html看起来应该是这个样子:
<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
<li>{{ choice.choice }}</li>
{% endfor %}
</ul>
模板系统使用“变量.属性”的方法来访问变量的属性值。在{{ poll.question }}这个例子中,Django先对poll做字典查询,不成功的话,就对进行属性查询——在这个例子属性查询成功了。如果属性查询也失败的话,会尝试调用poll对象的question()方法。
在{% for %}循环中有方法调用:poll.choice_set.all会解释为Python方法poll.choice_set.all(),该方法会返回一组可迭代的Choice对象,可以用于{% for %}标签中。
请参考template_guide来了解模板的更多内容。
简化URLconf
花点时间再复习复习视图和模板吧。刚才你看过URLconf设置了,也许下面的代码看上去有点冗余:
urlpatterns = patterns('',
(r'^polls/$', 'mysite.polls.views.index'),
(r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'),
(r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'),
(r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)
mysite.polls.views在这里重复出现了。
这是个很常见的情况,URLconf中可以对相同的方法前缀提供一个快捷方法。你可以把共同的方法前缀提取出来,将它作为patterns()的第一参数传入,就像下面这样:
urlpatterns = patterns('mysite.polls.views',
(r'^polls/$', 'index'),
(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
)
这跟前面格式的功能是一样的。它只是变得简洁了一些。
解耦URLconf
现在应该把投票程序的URL从Django项目配置中解耦出来了。Django程序是插件式的——这意味着只要做很小的修改,它就可以转移到另外一个Django项目中。
现在这个投票程序已经基本上解耦了,这是由于python manage.py startapp所创建的目录结构有这严格的规范,但是还是有一部分还是耦合在这个项目中:就是URLconf。
我们一直都是编辑mysite/urls.py里的URL设置,但是一个Django程序的URL设计应该由程序本身来规范,而不是在Django项目中规范——所以我们会在程序目录下面进行URL设置。
把mysite/urls.py拷贝到mysite/polls/urls.py。然后移除mysite/urls.py里和该程序有关的内容并插入一条include()函数,就像下面这样:
(r'^polls/', include('mysite.polls.urls')),
include()函数就仅仅是引用了另外一个URLconf设置。注意一下上面的正则表达式中并没有$符号结尾,而只有一个斜杠结尾。当Django碰到include()函数时,会将URL中匹配到部分移除,并将剩下的URL字符串传入到对应的URLconf设置中做进一步的处理。
现在看看匹配URL“/polls/34/”的情况:
l Django会找到匹配“^polls/”的部分。
l 然后,Django移除前面的“polls/”并将其余的部分“34/”传入到mysite.polls.urls中做进一步处理。
现在从每一行中移除前面的“polls/”,这样就完成了解耦:
urlpatterns = patterns('mysite.polls.views',
(r'^$', 'index'),
(r'^(?P<poll_id>\d+)/$', 'detail'),
(r'^(?P<poll_id>\d+)/results/$', 'results'),
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)
使用include()方法和URLconf解耦的初衷是为了方面做成即插即用式的URL。现在投票程序有自己独立的URLconf设置了,可以通过“/polls/”或者“/fun_polls/”甚至“/content/polls/”
来访问它,程序始终都能正常运行。
投票程序关注的只是相对链接,不是绝对链接。
熟悉了视图之后,进入第四部分来学习一下表单处理和通用视图。