咱们继续polls应用html
更新咱们的“polls/detail.html”,增长一个form:修改polls/templates/polls/detail.html
文件:web
<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="Vote" /> </form>
代码说明:正则表达式
上面的template为每一个问题的选择提供了一个radio button,每一个radio button的内容和choice的id联系在一块儿,这就是说,有人选择了问题的答案,而后提交,它将会经过post的方法提交choice=#,#表示所选中的choice的id,这就是最基本的html form表单;数据库
咱们设置表单的动做是{% url 'polls:vote' question.id %},提交方法是post,这是十分重要的,由于这个动做将会把数据提交到服务器,这不是django特例,而是一个良好的web开发习惯;django
forloop.counter表示for循环中的循环次数服务器
一旦咱们建立了一个post form表单,咱们须要考虑跨站请求问题,在django中只须要使用一个temaplate tag就能解决此问题,即在form表单中增长{%csrf_token %}app
如今咱们来建立一个view来提交这个表单,还记得在part3里边的一条url记录吗?函数
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
咱们也建立一个vote()的函数,增长到polls/views.py:oop
def vote(request,question_id): question = get_object_or_404(models.Question,pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST["choice"]) except (KeyError,models.Choice.DoesNotExist): return render(request,'polls/detail.html',{ 'question':question, 'error_message':'You do not select a choice.', }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results',args=(question.id,)))
代码说明:post
request.POST是一个类字典对象,能够经过key访问。在这个例子中,request.POST['choice'] 返回选择到的choice ID(以字符串的形式)。request.POST的值都是字符串,Djdango也提供request.GET用户获取数据。
request.POST['choice']若是没有取到数据,就会产生一个KeyError。并返回错误信息error_message。
自增vote数量,返回HttpResponseRedirect。HttpResponseRedirect有一个单独的参数:就是想要跳转的URL (see the following point for how we construct the URL in this case).This tip isn’t specific to Django; it’s just good Web development practice.
咱们使用 reverse() 函数在 HttpResponseRedirect 参数中,这个函数可以避免view中的硬编码。它可以将url name转化为url字符串或正则表达式。在这个例子中,reverse() 返回字符串以下所示:
'/polls/3/results/'
修改views.py中的results函数:
def results(request,question_id): question = get_object_or_404(models.Question,pk=question_id) return render(request,"polls/results.html",{ "question":question })
它和part3中的detail()很类似,只是template不同而已;
修改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 %}">Vote again?</a>
如今咱们到/polls/1/ 来为问题投票,每次投完票就能看见结果页面。若是大家有选择直接提交,就会看到错误信息。
注意:
咱们的vote() view还有个小问题,先从数据库获得selected_choice对象,而后计算数,而后返回数据库。若是有两个用户同时投票,就会出现问题。这叫竞赛,若是要解决这个问题,可使用F()来解决此问题。
像前面提到的detail() 和results() views 很是简单,还有index()view,这些 views 是一般Web 开发模式:从数据库中获得数据,经过url加载模板并渲染模板。由于这种模式太寻常了,django又给你们提供了快捷方式:“generic views” 系统;
下面把咱们的polls应用转变为generic views系统,咱们须要删除一大推代码,分红下面几步:
转换URLconf
删除一些老的,没用的views
来一些Django’s generic views
为何把代码搞来搞去?
一般的,当写一个Django app, 你须要先评估generic views 是否适合解决你的问题, 从开始就使用它们要比中间再改来的好。但在咱们的例子中是为了让你了解核心思想,就像在你使用计算器以前你要先学会基本运算。
打开polls/urls.py
并修改它:
from django.conf.urls import url from . import views app_name = 'polls' urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
咱们注意到将原来的<question_id>
改为了 <pk>
。
删除原来老的index, detail, and results三个views,使用新的generic views来代替:
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 .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' def vote(request,question_id): ...
咱们使用了两个generic views: ListView and DetailView. 分别表示 “display a list of objects” 和“display a detail page for a particular type of object.”。
每一个generic view须要知道它所对应的models名称
DetailView从url中的pk参数获取主键值,因此咱们已经在url中将question_id改为了pk了。
在默认状况下, DetailView generic view 将会使用一个叫作
类似的状况, the ListView generic view 使用默认模板文件
在上节中,咱们的模板文件中有 question 和latest_question_list这种变量. 在DetailView 中 ,一旦咱们使用django model,question 变量可以自动提供 Django会自动提供一个合适的环境变量名称。 然而, 对于ListView自动生成如question_list的变量名称。使用指定context_object_name变量,咱们也可以自定义变量名称。咱们能够在tempalte中将就django的自动产生变量,可是使用咱们本身定义的变量更加容易。
运行服务器,就能看到基于generic views的应用了,要详细了解generic views本身看文档。当你熟悉了 forms and generic views后,能够参看part 5查看如何测试django应用。