1. 写一个简单的表单form
a. 改写polls/template/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="Vote" />
</form>
先看效果:
<form action="{% url 'polls:vote' question.id %}" method="post">
这一句告诉浏览器, 使用post方法将form data返回给URL'polls:vote' question.id
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
这行代码为question的每个choice都配置了一个radio按钮, 该按钮的名字是'choice', 该按钮的值关联了choice.id. 当选择了其中一个按钮并按提交按钮时, 将会发送系统一个POST data: choice=id
{{ forloop.counter }}
是指for循环中当前循环次数
{% csrf_token %}
csrf是Cross Site Request Forgeries, 所有POST Form指向内部的URLs时需使用csrf_token语句.
b. 改写vote view处理表单提交的数据
提交了form之后, 需要创建与POST的URL对应的views.
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
from .models import Question, Choice
from django.urls import reverse
......
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): # KeyError是针对上一句中pk=request.POST来的, 当没有'choice' data时返回错误.
# 重新显示问题投票表单.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
# 在成功处理POST数据后, 需要返回一个HttpResponseRedirect, 防止用户按返回按钮时重复提交两次.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
request.POST是类似字典的对象, 可以让你通过键名来获取提交的数据. 在此例子中, request.POST['choice']以字符形式返回了选择的choice ID. request.POST的值永远是字符串. 类似的, Django也提供了request.GET来提取GET data.
selected_choice = question.choice_set.get(pk=request.POST['choice'])
reverse()函数目的是为了防止URL的hardcode(
指将可变变量用一个固定值来代替), 该函数格式是
reverse
(
viewname
,
urlconf=None
,
args=None
,
kwargs=None
,
current_app=None
) , 如果views对象可以接受参数传入, 则写在args中.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
c. 写vote()重定向的result view
在某人在question的detail页面投票之后, form表单内容被提交到'polls:vote'+question.id这个URL, URL调用vote页面, vote页面又重定向到该question的results页面.
在polls/views.py下重写results():
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
在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>
在浏览器输入http://127.0.0.1:8000/polls/1/后, 选择第一个choice后, 点击vote按钮, 则出现以下results页面
如果在投票页面没有选择任何choice就点击vote, 则会出现以下错误页面:
e. 小结, 上述几个URLs--Views--Templates之间的协作关系见下图.
2. 使用generic views简化代码
上述的views的一般套路是: 接收URL并提取参数--从数据库提取数据--用模板渲染数据--以Http响应返回模板.
Django提供generic views系统来替代上述流程.
该系统包括三步: a.转换URLconf, b.删除没用的views, c.基于generic views引入新的views.
a. 改写polls/urls.py
改之前:
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.index, name = 'index'),
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
改之后:
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name = 'index'), # views.index改为IndexView.as_view()
url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), # <question_id>改为<pk>, views.detail改为views.DetailView.as_view()
url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), # <question_id>改为<pk>, views.results改为views.ResultsView.as_view()
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),]
<question_id>改为<pk>是为了配合DetailView从URL中获取主键, 后面会介绍.
b. 改写polls/views.py
改之前:
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import Question, Choice
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {
'latest_question_list': latest_question_list,
}
return render(request, 'polls/index.html',context)
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
def vote(request, question_id):
# No change.
改写后:
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.views import generic
from .models import Question, Choice
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list' # 如果不指定, 则默认是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):
# No change.
ListView和DetailView是两种generic view. ListView是显示对象的清单, DetailView是显示对象的详细内容.
c. 在浏览器中验证, 页面与之前的一样.