上一个part我们创建了投票的内容页,但这个页面仅仅局限于静态展示,投票的“投”字还无从体现。接下来,我们就来看一下,如何把票投起来。
19.创建表单
我们来更新模板文件polls/detail.html,在其中加入“form”元素:
polls/templates/polls/detail.html :
<h1>{{ question.question_text }}</h1> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} {% for choice in question.choice_set.all %} <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="投票" /> </form>
简单讲解一下:
注意,别忘了将模板文件存为utf-8编码。
接下来,我们修改相应的视图,编辑polls/views.py文件,改成下面这样:
polls/views.py:
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect, HttpResponse from django.core.urlresolvers import reverse from polls.models import Choice, Question # ... def vote(request, question_id): p = get_object_or_404(Question, pk=question_id) try: selected_choice = p.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): # 没有选中投票项的,重新显示投票表单. return render(request, 'polls/detail.html', { 'question': p, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() # 在成功提交后,返回一个带有POST数据的HttpResponseRedirect 对象,
# 可防止用户在使用返回命令时提交两次数据。
return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))
讲解一下:
'/polls/3/results/'
上面的3就来自于“p.id”这个参数,这个重定向链接会调用”results”视图来显示最终的页面。
当用户完成投票后,vote视图会将链接转到结果页,接下来,我们就来编写结果页的视图。
编辑polls/views.py文件:
polls/views.py:
from django.shortcuts import get_object_or_404, render def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question})
这个视图和我们前面介绍过的detail视图很象,只是模板名称不同而已。
然后我们给这个视图配上相应的模板。创建一个模板文件polls/results.html,加入以下内容:
polls/templates/polls/results.html :
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">再投一次?</a>
下面,让我们访问链接http://127.0.0.1:8000/polls/1/,并开始投票:
成功投票之后,我们可以看到页面自动跳转到结果页,在这一页我们可以看到投票的数据:
你还可以试一下什么都不选的时候,点击投票按钮会有什么结果。
20.使用通用视图,精简代码
在Web开发中,我们追求“Less code is better”的理念。在Part3中的detail视图和这里的results视图非常相似,但代码略显冗余,可以进行精简。同样可精简的还有我们的index视图。
Django给我们提供了通用视图,我们可以在polls这个应用中使用通用视图,来把代码改造得更加简洁。
要使用通用视图,我们需要做以下操作:
首先我们来调整url配置文件,编辑polls/urls.py文件,改成这样:
polls/urls.py:
from django.conf.urls import patterns, url from polls import views urlpatterns = patterns('', url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P<pk>\d+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'), )
注意,url中的正则表达式发生了变化,第二、三个正则表达式中,我们把<question_id> 改成了<pk>。
下一步,我们把index,detail,results视图删除,换上Django的通用视图。编辑polls/views.py,把它改成下面这样:
polls/views.py:
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.views import generic from polls.models import Choice, Question class IndexView(generic.ListView): template_name = 'polls/index.html' context_object_name = 'latest_question_list' def get_queryset(self): """Return the last five published questions.""" return Question.objects.order_by('-pub_date')[:5] class DetailView(generic.DetailView): model = Question template_name = 'polls/detail.html' class ResultsView(generic.DetailView): model = Question template_name = 'polls/results.html'
我们使用了两个通用视图,ListView和DetailView。前一个视图用列表的方式来显示一组对象,后一个视图则用来显示某一特定对象的详细内容。
如果不给DetailView指定template_name属性的话,它将使用<应用名称>/<模型名称>_detail.html作为模板名称,在本例中,这个默认的模板名会是这个样子:polls/question_detail.html。因为我们的detail视图和results视图都使用了DetailView,所以要给它们分别指定template_name,以便用不同的模板来呈现各自的内容。
与此类似的是,如果不给ListView指定template_name属性,它将使用<应用名称>/<模型名称>_list.html作为模板名称,我们给index视图指定了template_name属性,让它使用我们已经做好的”polls/index.html“模板。
在Part3中,我们的视图向模板传递了question和latest_question_list变量,在DeltailView中,我们不需要传递question变量,它会自动将其发给模板。但是,在ListView中,它向模板自动提交的变量是question_list――这是一个完整的列表,而我们只需要提供最新的5个就足够了。我们希望用latest_question_list来代替默认的question_list,因此我们给模板提供了context_object_name属性。
做完上面这些,让我们再次在浏览器中查看我们的工作成果。
在Part5中,舍得将讲述如何测试我们的投票应用。
【未完待续】
本文版权归舍得学苑所有,欢迎转载,转载请注明作者和出处。谢谢!
作者:舍得
首发:舍得学苑@博客园