【Django第一步】第四部分:表单和通用视图
@[Django|翻译| python]
本教程将重点放在表单处理和削减我们的代码量
写一个简单的表单
让我们更新上一教程中的detail模板,使得它包含一个<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>
简要说明:
- 面的模板为每个问题选项显示一个单选按钮。每个选项的value是与之相关的每个问题的选项的id,每个选项的name值是”choice”。当有人选择其中一个单选按钮并提交表单时,它将发送POST数据choice=#,其中#是所选选项的ID。这是HTML表单的基本概念。
- 我们设置窗体的action为{% url 'polls:vote' question.id %}
,方法为post,使用method是非常重要的。
- forloop.counter
表示for循环经过的次数
- 由于我们正在创建POST表单(可能会影响修改数据),因此我们需要担心跨站点请求伪造。值得庆幸的是,您不必太担心,因为Django带有一个非常易于使用的系统来保护它。简而言之,所有以内部URL为目标的POST表单都应使用 模板标记。{% csrf_token %}
接下来,我们实现以下真正的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
是一个类似字典的对象,可让您通过键名访问提交的数据。在这种情况下,request.POST['choice']
以字符串形式返回所选选项的ID。request.POST['choice']
如果 在POST数据中没有提供choice,将会引发KeyError。如果choice没有给出,上面的代码会检查 并重新显示问题表单并显示错误消息。- 增加选择计数后,代码将返回一个
HttpResponseRedirect
而不是一个正常值 HttpResponse。HttpResponseRedirect
只有一个参数:用户将被重定向到的URL(关于这种情况下我们如何构造URL,请参阅以下几点)。正如上面的Python注释所指出的,您应该总是HttpResponseRedirect在成功处理POST数据之后返回一个 。这个提示并不是针对Django的; 这只是一个很好的Web开发实践 - 在这个例子reverse()中,我们在HttpResponseRedirect构造函数中使用了这个函数 。此功能有助于避免在视图功能中硬编码URL。它给出了我们想要传递控制权的视图的名称以及指向该视图的URL模式的可变部分
有人在问题中投票后,该vote()视图会重定向到Question的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/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()观点的代码确实有一个小问题。它首先selected_choice从数据库获取对象,然后计算新值votes,然后将其保存回数据库。如果您的网站的两个用户在同一时间尝试投票,这可能会出错:相同的值,比方说42,将被检索votes。然后,为两个用户计算并保存43的新值,但44将是预期值。
使用通用视图:代码越少越好
在detail()(从教程3)和results() 意见是非常简单的-并且如上面提到的,冗余的。index() 显示民意调查列表的视图与此类似。
这些视图代表了基本Web开发的常见情况:根据URL中传递的参数从数据库获取数据,加载模板并返回呈现的模板。由于这很常见,Django提供了一个称为“通用视图”系统的快捷方式。
通用视图将常见模式抽象到您甚至不需要编写Python代码来编写应用程序的地步。
我们将我们的投票应用程序转换为使用通用视图系统,以便我们可以删除一大堆我们自己的代码。我们只需采取几个步骤即可完成转换。我们会:
- 转换URLconf。
- 删除一些旧的不需要的视图。
- 基于Django的通用视图引入新的视图。
修改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'),
]
修改视图
我们删除老的index,detail,results视图,并使用Django的通用视图代替。为此,我们更改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属性给出。
- 该DetailView通用视图从URL中捕获key为 “pk”,所以我们已经不用question_id,而是以pk用于通用视图。