这里接着上一篇的内容继续从零搭建一个Django项目(三): 视图和模板
现在让我们检视下我们的投票系统已经完成了哪些功能:
- /polls/ -- 查看问题列表 √
- /polls/<question_id> -- 查看某一问题选项列表 √
- /polls/<question_id>/vote -- 投票
- /polls/<question_id>/result -- 查看投票结果
下面完善下投票功能和结果展示功能
创建一个简单的表单
让我们更新一下在上一个教程中编写的投票详细页面的模板 ("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>
复制代码
简要说明,form元素包裹了一个表单,表单中通过循环渲染了选项的列表,每个选项占一行,每行包括一个单选按钮和一个文字描述,选择后点击提交按钮,触发vote接口调用,调用方式为post并携带参数choice=#(因为radio标签的name属性为choice,#代表对应的value值也就是选项的id)
下面是vote视图的实现
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):
# Redisplay the question voting form.
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 的值永远是字符串。注意,Django 还以同样的方式提供 request.GET 用于访问 GET 数据 —— 但我们在代码中显式地使用 request.POST ,以保证数据只能通过 POST 调用改动。
-
如果在 request.POST['choice'] 数据中没有提供 choice , POST 将引发一个 KeyError 。上面的代码检查 KeyError ,如果没有给出 choice 将重新显示 Question 表单和一个错误信息。
-
在增加 Choice 的得票数之后,代码返回一个 HttpResponseRedirect 而不是常用的 HttpResponse 、 HttpResponseRedirect 只接收一个参数:用户将要被重定向的 URL(请继续看下去,我们将会解释如何构造这个例子中的 URL)。
-
在这个例子中,我们在 HttpResponseRedirect 的构造函数中使用 reverse() 函数。这个函数避免了我们在视图函数中硬编码 URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。 在本例中,使用在 教程第 3 部分 中设定的 URLconf, reverse() 调用将返回一个这样的字符串:
'/polls/3/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})
复制代码
再为其创建一个模板
<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>
复制代码
至此已经完成了一个简易投票系统所有基础功能的开发
目录结构如下
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
templates
polls/
detail.html # 详情页模板
index.html # 问题列表页模板
results.html # 问题结果页模板
models.py # 模型定义
tests.py
urls.py # URL配置
views.py # 视图编写
复制代码
通用视图
在完成项目基础功能后,回头整体审视下,可以发现大部分视图其实具备相同的流程,Django也为此提供了一次通用视图来减少重复的代码量,改进视图如下:
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 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):
... # same as above, no changes needed.
复制代码
我们在这里使用两个通用视图: ListView 和 DetailView 。这两个视图分别抽象“显示一个对象列表”和“显示一个特定类型对象的详细信息页面”这两种概念。
- 每个通用视图需要知道它将作用于哪个模型。 这由 model 属性提供。
- DetailView 期望从 URL 中捕获名为 "pk" 的主键值,所以我们为通用视图把 question_id 改成 pk 。
url改进如下
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'),
]
复制代码
详细用法,可以参考Django官方文档
近期评论