一个简单的表单
让我们更新一下投票详细页面的模板 polls/detail.html,让它包含一个HTML <form>
元素:
<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>
- 设置表单的 action 为 {% url 'polls:vote' question.id %}。method="post",这个提交表单的行为会改变服务器端的数据。
- 所有针对内部URL的 POST 表单都应该使用 {% csrf_token %} 模板标签。
- 在 Question 的每个 Choice 前添加一个单选按钮。每个单选按钮的 value 属性是对应的各个 Choice 的 ID。每个单选按钮的 name 是 "choice" 。这意味着,当有人选择一个单选按钮并提交表单时,将发送一个 POST 数据 choice=# ,其中# 为选择的 Choice 的 ID。
- forloop.counter 指示 for 标签已经循环多少次。
现在,让我们来创建一个 Django 视图来处理提交的数据。我们为polls应用创建了一个URLconf,包含这一行:
path('<int:question_id>/vote/', views.vote, name='vote'),
我们来创建一个vote()
函数的虚拟实现。将下面的代码添加到polls/views.py
:
from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse from .models import Choice, Question def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
- request.POST 是一个类字典对象,可以通过关键字的名字获取提交的数据。在这个例子中,request.POST['choice'] 以字符串形式返回选择的 Choice 的 ID。 request.POST 的值永远是字符串。如果在 request.POST['choice'] 数据中没有提供 choice , POST 将引发一个 KeyError 。上面的代码检查 KeyError ,如果没有给出 choice 将重新显示 Question 表单和一个错误信息。
- HttpResponseRedirect 只接收一个参数:用户将要被重定向的 URL。在这个例子中,我们在 HttpResponseRedirect 的构造函数中使用 reverse() 函数,需要我们给出想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。这个函数避免了我们在视图函数中硬编码 URL。
reverse() 调用将返回一个这样的字符串:'/polls/3/results/'。其中 3
是 question.id
的值。重定向的 URL 将调用 results
视图来显示最终的页面。
当有人对 Question 进行投票后, vote()
视图将请求重定向到 Question 的结果界面。让我们来编写这个视图:
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})
创建一个 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/
然后为 Question 投票。你应该看到一个投票结果页面,并且在你每次投票之后都会更新。如果你提交时没有选择任何 Choice,会看到错误信息。
使用通用视图
detail()
和 results()
视图都很简单,但是存在冗余问题。
这些视图反映基本的 Web 开发中的一个常见情况:根据 URL 中的参数从数据库中获取数据、载入模板文件然后返回渲染后的模板。 由于这种情况特别常见,Django 提供一种快捷方式,叫做“通用视图”系统。
通用视图将常见的模式抽象化,可以使你在编写应用时甚至不需要编写Python代码。
我们将polls应用转换成使用通用视图系统,可以删除许多代码,仅仅需要做以下几步来完成:
1. 转换 URLconf;
2. 删除旧的,不再需要的视图;
3. 基于通用视图引入新的视图。
改良 URLconf
修改 polls/urls.py 文件:
from django.urls import path from . import views app_name = 'polls' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'), path('<int:pk>/results/', views.ResultsView.as_view(), name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ]
改良视图
删除旧的 index
, detail
, 和 results
视图,并用 Django 的通用视图代替。修改 polls/views.py
文件:
from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls 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 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): return HttpResponse("You're voting on question %s." % question_id)
我们在这里使用两个通用视图:ListView
和DetailView
。这两个视图分别抽象"显示一个对象列表"和"显示一个特定类型对象的详细信息页面"。
- 每个通用视图需要知道它将作用于哪个模型。这由
model
属性提供。 DetailView
期望从URL中捕获名为 pk 的主键值,所以我们把 question_id 改成 pk 。