本教程从以下位置开始 Tutorial 3 停下来了。我们将继续使用网络投票应用程序,并将重点放在表单处理和减少代码上。
从哪里获得帮助:
如果您在阅读本教程时遇到困难,请访问 Getting Help 部分的常见问题解答。
让我们更新上一个教程中的民意调查详细信息模板(“polls/Detail.html”),以便该模板包含HTML <form>
元素:
polls/templates/polls/detail.html
¶<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% 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 %}
</fieldset>
<input type="submit" value="Vote">
</form>
快速概述:
上面的模板为每个问题选择显示一个Radio按钮。的 value
每个Radio按钮的ID是关联问题选项的ID。 name
每个Radio按钮的大小 "choice"
.这意味着,当某人选择其中一个Radio按钮并提交表单时,它将发送Post数据 choice=#
其中#是所选选择的ID。这是HTML表单的基本概念。
我们设置了表单的 action
至 {% url 'polls:vote' question.id %}
,然后我们设置 method="post"
。vbl.使用 method="post"
(相对于 method="get"
)非常重要,因为提交此表单的行为将改变数据服务器端。每当您创建更改数据服务器端的表单时,请使用 method="post"
。这篇技巧并不是专门针对Django的;总的来说,它是很好的Web开发实践。
forloop.counter
指示有多少次 for
标签已完成循环
由于我们正在创建一个Post表单(它可能具有修改数据的效果),因此我们需要担心跨站点请求伪造。值得庆幸的是,您不必太担心,因为Django附带了一个有用的系统来预防它。简而言之,所有针对内部URL的Post表单都应该使用 {% csrf_token %}
模板标签。
现在,让我们创建一个Django视图,该视图处理提交的数据并对其执行一些操作。 Tutorial 3 ,我们为民意调查应用程序创建了一个URLinf,其中包括以下行:
polls/urls.py
¶path("<int:question_id>/vote/", views.vote, name="vote"),
我们还创建 vote()
功能让我们创建一个真实版本。将以下内容添加到 polls/views.py
:
polls/views.py
¶from django.db.models import F
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 = F("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
值始终是字符串。
请注意,Django还提供 request.GET
以相同的方式访问GET数据--但我们明确使用 request.POST
在我们的代码中,以确保数据仅通过Post调用更改。
request.POST['choice']
将筹集 KeyError
如果 choice
未在Post数据中提供。上面的代码检查 KeyError
并重新显示问题表格并显示错误消息,如果出现 choice
没有给出。
F("votes") + 1
instructs the database 将计票数增加1。
增加选择计数后,代码返回 HttpResponseRedirect
而不是正常的 HttpResponse
。 HttpResponseRedirect
采用一个参数:用户将被重定向到的URL(请参阅以下一点,了解我们在这种情况下如何构建URL)。
正如上面的Python注释所指出的,您应该始终返回一个 HttpResponseRedirect
在成功地处理了POST数据之后。这篇技巧并不是专门针对Django的;总的来说,它是很好的Web开发实践。
我们使用 reverse()
中的函数 HttpResponseRedirect
本例中的构造函数。此函数有助于避免必须在视图函数中硬编码URL。它给出了我们想要传递控制权的视图的名称以及指向该视图的URL模式的变量部分。在本例中,使用我们在中设置的URLinf Tutorial 3 ,这个 reverse()
调用将返回一个字符串,例如::
"/polls/3/results/"
凡. 3
的价值是 question.id
.此重定向的URL随后将调用 'results'
查看以显示最终页面。
所称 Tutorial 3 , request
是一种 HttpRequest
object.欲了解更多关于 HttpRequest
对象,请参阅 request and response documentation 。
有人对问题投票后, vote()
查看重定向到问题的结果页面。让我们写下这个观点:
polls/views.py
¶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})
这几乎与 detail()
view from Tutorial 3 .唯一的区别是模板名称。我们稍后将修复此冗余。
现在,创建 polls/results.html
模板:
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>
现在,转到 /polls/1/
在您的浏览器中并对问题进行投票。您应该看到每次投票时都会更新的结果页面。如果您在未选择选择的情况下提交表格,您应该会看到错误消息。
这个 detail()
(发件人 Tutorial 3 )和 results()
观点非常短--而且,如上所述,是多余的。的 index()
显示民意调查列表的视图类似。
这些视图代表了基本Web开发的常见情况:根据URL中传递的参数从数据库获取数据,加载模板并返回呈现的模板。由于这种情况非常常见,Django提供了一种快捷方式,称为“通用视图”系统。
泛型视图将常见模式抽象到您甚至不需要编写Python代码来编写应用程序的程度。例如, ListView
和 DetailView
泛型视图分别抽象了“显示对象列表”和“显示特定类型对象的详细信息页”的概念。
让我们将我们的民意调查应用程序转换为使用通用视图系统,这样我们就可以删除一堆我们自己的代码。我们必须采取一些步骤来进行转换。我们将:
转换URLinf。
删除一些旧的、不需要的视图。
根据Django的一般观点引入新观点。
请继续阅读以了解详细信息。
为什么要进行代码洗牌?
通常,在编写Django应用程序时,您会评估通用视图是否适合您的问题,并且您会从一开始就使用它们,而不是中途重构您的代码。但到目前为止,本教程故意专注于“艰难的方式”编写视图,以专注于核心概念。
在开始使用计算器之前,您应该了解基本的数学。
首先打开 polls/urls.py
URLinf并将其更改如下:
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"),
]
请注意,第二个和第三个模式的路径字符串中匹配的模式的名称已从 <question_id>
至 <pk>
。这是必要的,因为我们将使用 DetailView
通用视图将取代我们的 detail()
和 results()
视图,并且它期望从URL捕获的主键值被调用 "pk"
。
接下来,我们要删除旧的 index
, detail
,以及 results
视图并使用Django的通用视图。为此,请打开 polls/views.py
文件并将其更改如下:
polls/views.py
¶from django.db.models import F
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.
...
每个通用视图都需要知道它将在哪个模型上操作。这是使用 model
属性(在本例中, model = Question
为 DetailView
和 ResultsView
)或通过定义 get_queryset()
方法(如中所示 IndexView
)。
默认情况下, DetailView
通用视图使用名为 <app name>/<model name>_detail.html
.在我们的例子中,它将使用模板 "polls/question_detail.html"
。这个 template_name
属性用于告诉Django使用特定的模板名称,而不是自动生成的默认模板名称。我们还指定 template_name
对于 results
列表视图--这确保了结果视图和详细信息视图在渲染时具有不同的外观,即使它们都是 DetailView
幕后。
类似地, ListView
通用视图使用名为 <app name>/<model name>_list.html
;我们使用 template_name
告诉 ListView
使用我们现有的 "polls/index.html"
模板。
在本教程的前几部分中,为模板提供了包含 question
和 latest_question_list
上下文变量。为 DetailView
这个 question
变量是自动提供的--因为我们使用的是Django模型 (Question
),Django能够为上下文变量确定合适的名称。然而,对于ListView,自动生成的上下文变量是 question_list
.为了覆盖这一点,我们提供了 context_object_name
属性,指定我们要使用的 latest_question_list
而不是.作为另一种方法,您可以更改模板以匹配新的默认上下文变量--但告诉Django使用您想要的变量要容易得多。
运行服务器,并根据通用视图使用新的民意调查应用程序。
有关通用视图的完整详细信息,请参阅 generic views documentation 。
当您对表格和通用视图感到满意时,请阅读 part 5 of this tutorial 了解测试我们的民意调查应用程序。
5月 28, 2025