接下来我们需要设计视图和页面了,一共要有四个视图:index,detail,vote,results,相应的也会有四个页面。
URL映射
首先需要解决的就是url映射,django在使用URL时首先找到ROOT_URLCONF这个设置(在settings.py里面),然后加载这个module,寻找里面的一个叫做urlpatterns的变量。它是一个元组,其中每一项的格式是“(regular expression,callback function[,optional dictionary])”。找到这个变量之后就从头到尾地进行匹配,匹配成功之后就调用回调函数,并将HttpRequest作为第一个参数。
在urls.py里添加关于投票的url映射信息:
urlpatterns = patterns(’’,url(r’^polls/$’, ’polls.views.index’),url(r’^polls/(?P\d+)/$’, ’polls.views.detail’),url(r’^polls/(?P \d+)/results/$’, ’polls.views.results’),url(r’^polls/(?P \d+)/vote/$’, ’polls.views.vote’),url(r’^admin/’, include(admin.site.urls)),)
实现视图和模版
这里的所有view都没有实现,那么,下面就简单实现一下了:
from django.http import HttpResponsedef index(request): return HttpResponse("Hello, world. You’re at the poll index.")def detail(request, poll_id): return HttpResponse("You’re looking at poll %s." % poll_id)def vote(request, poll_id): return HttpResponse("You’re voting on poll %s." % poll_id)def results(request, poll_id): return HttpResponse("You’re looking at the results of poll %s." % poll_id)
这仅仅是个最简单的示例,以后还要一一实现。要注意,每一个view要么返回一个HttpResponse,要么抛出Http404异常。
index视图是用来显示投票列表的,为了不显示太多,我们把它修改为只显示最近的5个:
from django.http import HttpResponsefrom polls.models import Polldef 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))
这里用到的就是django里面的模版渲染:载入一个html模版,然后将context传入进去。context是一个字典,字典的项就是模版中要用到的变量。
我们之前设置过所有模版的根文件夹,即TEMPLATE_DIRS这个变量,只需要在下面建立polls/index.html就行了:
{% if latest_poll_list %}
- {% for poll in latest_poll_list %}
- { { poll.question }} {% endfor %}
No polls are available.
{% endif %}如果现在运行服务器,就可以看到这个页面了,显示的内容就是所有的投票的问题。
两个捷径
第一个是django提供的模版渲染捷径:render_to_response()。这个方法有两个参数:模版页面和词典,因此上个例子可以改为:return render_to_response("polls/index.html",{'latest_poll_list':latest_poll_list})
在index页面的每一项会超连接到detail页面,首先会根据投票的id从数据库中找到相应的对象,当然,如果找不到就会抛出ObjectDoesNotExist异常,因此,detail视图应该是这样的:
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})
这就要引出我们说的第二个捷径了:get_object_or_404。它将一个model作为第一个参数,把get object时用到的一系列关键词作为第二个参数。因此上面的try except可以合并为一句:p = get_object_or_404(Poll, pk=poll_id)
要多说的是,如果真的出现了404错误,django就会调用URLConf中的handler404这个视图,你需要定义它,但如果没有,就默认调用django.views.defaults.page_not_found()这个视图。此外,还要自己实现一个404.html,因为本来没有这个页面,再加上DEBUG被设为False的话,就会产生HTTP500,500的意思是server error。当然,你也可以像404错误一样设定handler500.
改进URL映射
patterns的第一个参数的意思是下面所有view的共同前缀,因为关于投票的四个视图都以polls.views开头,因此可以改为:
urlpatterns = patterns(’polls.views’,url(r’^polls/$’, ’index’),url(r’^polls/(?P\d+)/$’, ’detail’),url(r’^polls/(?P \d+)/results/$’, ’results’),url(r’^polls/(?P \d+)/vote/$’, ’vote’),)
但是admin和它们又没有共同前缀,因此可以写成这样:
urlpatterns = patterns(’polls.views’,url(r’^polls/$’, ’index’),url(r’^polls/(?P\d+)/$’, ’detail’),url(r’^polls/(?P \d+)/results/$’, ’results’),url(r’^polls/(?P \d+)/vote/$’, ’vote’),)urlpatterns += patterns(’’,url(r’^admin/’, include(admin.site.urls)),)
前面说过,每一个application都是可插拔的,它独立于整个工程存在,但是这里的urls.py是在project文件夹下面的,也就是说,关于投票这个应用的url信息出现了工程的文件夹下。所以,为了实现松耦合,因此我们需要做一些改动:将mysite文件夹下的urls.py拷贝到polls文件夹下面,并将mysite/urls.py改为:
from django.conf.urls import patterns, include, urlfrom django.contrib import adminadmin.autodiscover()urlpatterns = patterns(’’,url(r’^polls/’, include(’polls.urls’)),url(r’^admin/’, include(admin.site.urls)),)
include语句调用了另一个URLConf,这时,django匹配过程变为首先找到polls/,如果匹配,将剩下的部分传递给引用的URLConf中。因此,polls/url.py应为:
from django.conf.urls import patterns, urlurlpatterns = patterns(’polls.views’,url(r’^$’, ’index’),url(r’^(?P\d+)/$’, ’detail’),url(r’^(?P \d+)/results/$’, ’results’),url(r’^(?P \d+)/vote/$’, ’vote’),)
之前有一个模版文件index.html里用到了引用<a href="/polls/{
{ poll.id }}/">{ { poll.question }}</a> ,现在应改为:<a href="{% url polls.views.detail poll.id %}">{ {poll.question}}</a>。django的url语法是这样的,{%url path-to-view args1 args2 %}.更改detail模版
我们之前的polls/detail.html模版仅仅是显示出了问卷的问题,让我们把这个模版改一下,里面加上一个form表单:
{ {poll.question}}
{% if error_message %}{ { error_message }}
{% endif %}
由于我们使用了csrf_token标签,因此就不能用context了,而要用RequestContext:
return render_to_response(’polls/detail.html’, {’poll’: p},context_instance=RequestContext
(request))表单提交后到了vote视图:
def vote(request,poll_id): #找到传进来的poll p=get_object_or_404(Poll,pk=poll_id) try: #之前表单传入的参数是choice,值是choice_id,这里使用request.POST方法将它的值提取出来 selected_choice=p.choice_set.get(pk=request.POST['choice']) #提取错误时,显示错误信息 except(KeyError,Choice.DoesNotExist): return render_to_response('polls/detail.html',{ 'poll':p, 'error_message':"You didn't select a choice.", },context_instance=RequestContext(request)) #没有错误就给选中的那一项投一票,然后保存,将来写入到数据库中 else: selected_choice.votes+=1 selected_choice.save() #页面跳转到results视图 return HttpResponseRedirect(reverse('polls.views.results',args=(p.id,)))
reverse就是指定跳转的视图,定义是reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)。results视图用来显示结果:
def results(request,poll_id): p=get_objects_or_404(Poll,pk=poll_id) return render_to_response('polls/results.html',{ 'poll':p})
results.html的内容:
{ { poll.question }}
- {% for choice in poll.choice_set.all %}
- { { choice.choice_text }} -- { { choice.votes}} vote{ { choice.votes|pluralize }} {% endfor %}
最后不说了,上图: