Django笔记 3
接下来我们需要设计视图和页面了,一共要有四个视图: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<poll_id>\d+)/$’, ’polls.views.detail’), url(r’^polls/(?P<poll_id>\d+)/results/$’, ’polls.views.results’), url(r’^polls/(?P<poll_id>\d+)/vote/$’, ’polls.views.vote’), url(r’^admin/’, include(admin.site.urls)), )
实现视图和模版
这里的所有view都没有实现,那么,下面就简单实现一下了:
from django.http import HttpResponse def 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 HttpResponse from polls.models import Poll 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))
这里用到的就是django里面的模版渲染:载入一个html模版,然后将context传入进去。context是一个字典,字典的项就是模版中要用到的变量。
我们之前设置过所有模版的根文件夹,即TEMPLATE_DIRS这个变量,只需要在下面建立polls/index.html就行了:
{% if latest_poll_list %} <ul> {% for poll in latest_poll_list %} <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %}
如果现在运行服务器,就可以看到http://localhost:8000/polls/这个页面了,显示的内容就是所有的投票的问题。
两个捷径
第一个是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<poll_id>\d+)/$’, ’detail’), url(r’^polls/(?P<poll_id>\d+)/results/$’, ’results’), url(r’^polls/(?P<poll_id>\d+)/vote/$’, ’vote’), )
但是admin和它们又没有共同前缀,因此可以写成这样:
urlpatterns = patterns(’polls.views’, url(r’^polls/$’, ’index’), url(r’^polls/(?P<poll_id>\d+)/$’, ’detail’), url(r’^polls/(?P<poll_id>\d+)/results/$’, ’results’), url(r’^polls/(?P<poll_id>\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, url from django.contrib import admin admin.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, url urlpatterns = patterns(’polls.views’, url(r’^$’, ’index’), url(r’^(?P<poll_id>\d+)/$’, ’detail’), url(r’^(?P<poll_id>\d+)/results/$’, ’results’), url(r’^(?P<poll_id>\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表单:
<!-- 显示出这个投票的题目 --> <h1>{{poll.question}}</h1> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <!-- 表单提交到vote视图 --> <form action=" {% url polls.views.vote poll_id=poll.id %}" method="post"> <!-- 为了防止跨域攻击,使用下面的标签 --> {% csrf_token %} {% for choice in poll.choice_set.all %} <!-- value值为选项的choice_id,因此,当表单提交的时候也就提交了choice_id,即"choice=choice_id" --> <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> <label for="choice{{ forloop.counter }}">{{ choice.choice_text}} </label> <br/> {% endfor %} <input type="submit" value="vote"/> </form>
由于我们使用了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的内容:
<h1>{{ poll.question }}</h1> <ul> {% for choice in poll.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes}} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url polls.views.vote poll_id=poll.id %}">Vote again?</a>
最后不说了,上图: