编写你的第一个Django程序,第三部分
本文继续第二部分所讨论的内容。我们会继续开发网络投票程序并深入研究怎样创建公共接口——使用“视图”。
哲理
在Django程序中的“视图”是一种可以有着独立功能和模板的网页。比如,在一个博客程序中,你可能有以下视图:
l 博客首页——显示最新的日志。
l 日志详细页面——日志的永久链接页面。
l 基于年份的归档页面——显示给定年份所有有日志的月份。
l 基于月份的归档页面——显示给定月份所有有日志的日期。
l 基于日期的归档页面——显示给定日期所有的日志。
l 评论功能——处理日志的评论。
在本文的投票程序中,我们有下面的四种视图:
Poll归档页面——显示最新的所有投票。
Poll详细页面——显示一个投票的问题,可以进行投票但是不显示投票结果。
Poll结果页面——显示一个投票的结果。
投票功能——处理投票的选项。
在Django中,每个视图都是一个Python函数。
设计你的URL
编写视图的第一步就是设计你的URL结构。你需要创建一个叫做URLconf的Python模块。URLconf是连接指定的URL和Python代码的纽带。
当用户请求Django页面时,系统会查找ROOT_URLCONF设置,这是个Python模块的字符串。Django会加载这个模块并查找一个叫做urlpatterns的模块变量,这个变量是由遵循下面格式的元组所组成的序列:
(regular expression, Python callback function [, optional dictionary])
Django从第一个正则表达式开始进行迭代,将当前的URL与正则式进行比较直到找到匹配成功的纪录。
找到匹配的正则表达式,Django会调用对应的Python回调函数,并传入一个HttpRequest对象作为第一参数,任何根据正则式提取出的值将作为关键字参数传入,而optional dictionary中的值也会作为关键字参数传入。
要了解更多HttpRequest,请参考Request and response objects部分。要了解更多URLconfs,请参考URL dispatcher部分。
在第一部分里,当你运行python django-admin.py startproject mysite时,就已经在mysite/urls.py创建了一个默认的URLconf设置。它会自动将ROOT_URLCONF(在settings.py文件中)设置指向这个文件:
ROOT_URLCONF = 'mysite.urls'
下面是个例子。编辑mysite/urls.py,修改成如下代码:
from django.conf.urls.defaults import *
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'),
)
我们要来审核一下上面的内容。当有人在你的网站上请求一个页面——例如“/polls/23/”,Django会加载mysite.url这个模块,因为ROOT_URLCONF设置已经指向它。在这个模块中,程序会查找一个叫做urlpatterns的变量并遍历其中的正则式。当它找到一个匹配“r'^polls/(?P<poll_id>\d+)/$'”的表达式时,程序会加载对应的模块:mysite.polls.views.detail。这个模块就是mysite/polls/views.py中的函数detail()。最后,程序会像下面这样调用detail()函数:
detail(request=<HttpRequest object>, poll_id='23')
poll_id=’23’这个部分来自(?P<poll_id>\d+)。用括号包围一个正则式会捕获匹配这个正则式的文字并将捕获到的文字作为视图中对应函数的参数传入;?P<poll_id>这个部分会给匹配到的内容赋予一个名称;\d+则是用来匹配一串数字的正则表达式。
由于URLconf设置都是正则表达式,所以你可以毫无限制地来使用它们。而且你也没有必要加上多余的URL部分,比如.php这样的扩展名——如果你像下面这样做,那只能说你天赋异禀、骨骼精奇,太异于常人了:
(r'^polls/latest\.php$', 'mysite.polls.views.index'),
真的,千万别这样,这太雷人了。
请注意一下,在正则式中并没有查找GET或是POST参数以及域名。例如,在请求http://www.example.com/myapp/时,URLconf会查找/myapp/。在请求http://www.example.com/myapp/?page=3时,URLconf还是查找/myapp/。
如果你在正则表达式上有困难,请看看维基百科和Python文档。当然,O’Reilly出版的图书“Mastering Regular Expressions”(Jeffrey Friedl著)也是非常不错的。
最后,一个有关性能上的提示:这些正则表达式在第一次加载URLconf的时候就进行编译。它们的速度超快。
编写你的第一个视图
现在我们还没创建任何视图——我们只是设置了URLconf而已。但是我们要先确认Django会遵循这些URLconf的设置。
启动Django开发服务器:
python manage.py runserver
现在访问http://localhost:8000/polls/,你应该会得到如下的错误信息:
ViewDoesNotExist at /polls/
Tried index in module mysite.polls.views. Error was: 'module'
object has no attribute 'index'
产生这个错误的原因是因为你还没有在mysite/polls/views.py编写index()函数。
试试访问“/polls/23/”、“/polls/23/results/”和“/polls/23/vote/”。这些错误信息会告诉你Django会调用哪些视图函数(调用失败的信息,因为你还没有编写任何视图)。
现在开始编写第一个视图。打开mysite/polls/views.py加入下面的Python代码:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the poll index.")
这就是个最简单的视图了。现在在浏览器里输入“/polls/”,就能看到输出的文字。现在加入下面的代码。它有点不一样,因为它带有一个参数(注意,这个参数是根据URLconf的正则表达式所捕获的内容):
def detail(request, poll_id):
return HttpResponse("You're looking at poll %s." % poll_id)
然后在浏览器里输入“/polls/34/”,就能在页面上看到你在URL中输入的ID值。
让视图真正发挥作用
每个视图函数都必须要做以下两件事情中的一件:返回一个包含页面内容的HttpResponse对象,或者抛出一个异常,比如Http404。剩下的就是你自己的工作了。
视图可以从数据库中读取纪录。它可以使用Django的模板系统,也可以使用第三方的Python模板系统。它可以生成PDF文件,输出XML,实时生成ZIP文件等等,使用Python库你可以做任何你想做的事情。
而Django想要的就是个HttpResponse对象,或者是个异常。
这真的很方便,现在我们用上第一部分提到过的数据库API来做点东西。修改一下index()视图函数,让它显示最新的5个投票问题,每个问题之间用逗号问分割并按照发布时间降序排列:
from mysite.polls.models import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
output = ', '.join([p.question for p in latest_poll_list])
return HttpResponse(output)
但是这有个问题,页面的设计是在代码中写死的。如果你想改变页面的外观,就必须修改Python代码了。现在我们用Django模版系统将页面设计从Python代码中分离出来:
from django.template import Context, loader
from mysite.polls.models import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
t = loader.get_template('polls/index.html')
c = Context({
'latest_poll_list': latest_poll_list,
})
return HttpResponse(t.render(c))
上面的代码会加载“polls/index.html”模板并传入一个context对象。这个context对象是一个关联了模板变量和Python对象的字典。
重新加载页面,你会看到一个错误信息:
TemplateDoesNotExist at /polls/
polls/index.html
现在还没创建模板呢。先在你的系统上建立个Django能够访问的文件夹。(Django就像你的服务器一样运行。)但是别把模板放在文档根目录下面。为了安全,你不能把它们设置为公共权限。然后修改settings.py中的TEMPLATE_DIRS加入刚才的创建的文件路径,让Django能够找到模板——就如同你在第二部分中所做的一样。
上面完成之后,在模板目录下面创建polls目录,然后在该目录下创建index.html文件。注意一下代码里的loader.get_template('polls/index.html')会加载[template_directory]/polls/index.html这个文件。
在模板文件中加入下面代码:
{% if latest_poll_list %}
<ul>
{% for poll in latest_poll_list %}
<li>{{ poll.question }}</li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
在浏览器中重新加载页面,你能看到一组列表中包含“What’s up”这个投票。
快捷方法:render_to_response()
加载模板,传入context对象并返回一个包含了渲染后的模板内容的HttpResponse对象是一套连贯的操作。Django提供了快捷方法来一步完成这些工作。下面是重写过的index()方法:
from django.shortcuts import render_to_response
from mysite.polls.models import Poll
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})
注意一下一旦我们在视图中使用了这些方法,就没有必要再导入load、Context和HttpResponse了。
render_to_response()方法接收模板名称作为第一参数,还可以接收一个字典作为可选的第二参数。改函数会根据传入的字典进行渲染模板,并返回HttpResponse对象。