五、表单和通用视图
1、表单
Django提供的表单相比HTML表单可以对数据进行更加复杂的验证工作,重写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="Vote" />
</form>
PS:上报’polls’ is not a registered namespace错误,需要在polls/urls.py增加以下代码:
app_name = 'polls'
error_message为当你未选择任一单选按钮就提交表单时,提示错误。
表单包含两个属性:action和method。action为当你提交表单时,向何处发送表单数据。method为提交表单的方式,有get和post两种。后面还有一个{% csrf_token %}标签,csrf全称是Cross Site Request Forgery,这是Django提供的防止伪装提交请求的功能。post方法提交表单时,必须有此标签。
forloop.counter表示for标签已经经过了多少次循环。
label的for属性规定label与哪个元素绑定。
从表单上action属性可知,将由vote/question.id页面处理表单数据,需要重写polls/views.py里的vote()函数,代码如下:
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
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()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
request.POST可以看成一个字典,可以通过key去获取提交的数据。request.POST[‘choice’] 获取你选取的radio的value值choice.id,并保存为string。这也可以用request.GET.get(‘key’, None)来取值,没有时不报错,request.GET类似。
增加选择计数后,代码返回一个HttpResponseRedirect而不是一个正常的HttpResponse。 HttpResponseRedirect只有一个参数:用户将被重定向到的URL。原因见注释。在成功处理POST数据之后,应该总是返回一个HttpResponseRedirect。
reverse()函数可以避免在视图功能中硬编码URL,参数为想要跳转的页面的视图名称以及指向该视图的URL模式的可变部分。在这里,这个reverse()调用将会返回一个类似的字符串’/polls/3/results/’。
某人投票后,vote()函数将会跳转到结果页面,需要重写polls/views.py的results()函数,代码如下:
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/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>
PS.我们代码中是从数据库中获取对象,然后计算新的值votes,然后将其保存回数据库。这里有个问题。如果您的网站的两个用户尝试在同一时间投票,这可能会出错:相同的值,比如说42,将被检索votes。然后,为两个用户计算并保存43的新值,但44将是期望值。这种情况被称为race condition,处理方法见Avoiding race conditions using F()。
2、使用通用视图:代码越少越好
Django的通用视图抽象出一些在视图开发中常用的代码和模式,这样就可以在无需编写大量代码的情况下,快速编写出常用的数据视图。
Django内建通用视图可以实现如下功能:
- 完成常用的简单任务: 重定向到另一个页面以及渲染一个指定的模板。
- 显示列表和某个特定对象的详细内容页面。
- 呈现基于日期的数据的年/月/日归档页面,关联的详情页面,最新页面。Django Weblogs(http://www.djangoproject.com/weblog/)的年、月、日的归档就是使用通用视图架构的,就像是典型的新闻报纸归档。
综上所述,这些视图为开发者日常开发中常见的任务提供了易用的接口。下面主要介绍DetailView和ListView通用视图的使用,想了解更多信息请参考Built-in class-based views API。
声明视图类
将旧的index、detail和results视图函数注释,在polls/views.py中声明视图类:
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
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属性用于绑定模型,说明获取数据。
template_name属性用于说明使用特定的模板名称而不是自动生成的默认模板名称,确保结果视图和细节视图在呈现时具有不同的外观。
URL配置
修改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'),
]
第二个和第三个path的路径字符串已从更改为。因为DetailView通用视图将从URL中捕获的主键值称为 “pk”,所以在通用视图中,需要将question_id改以pk。
参考资料