Django教程:编写投票应用第四部分 - 表单处理与通用视图
表单处理基础
在Django投票应用的这一部分,我们将重点学习如何处理表单数据并简化视图代码。表单是Web开发中用户交互的核心组件,Django提供了强大的工具来处理表单数据。
创建投票表单
首先,我们需要更新投票详情页面的模板,添加一个表单让用户进行投票:
<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>
这个表单有几个关键点需要注意:
- 使用POST方法提交数据,因为这会修改服务器端数据
- 包含CSRF令牌以防止跨站请求伪造攻击
- 每个选项的值设置为对应选项的ID
- 使用forloop.counter生成唯一的ID属性
处理表单提交
接下来,我们需要实现处理投票的视图函数:
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):
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()
return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
这个视图函数做了以下几件事:
- 获取问题对象或返回404错误
- 尝试从POST数据中获取用户选择的选项
- 如果用户没有选择选项,重新显示表单并提示错误
- 使用F()表达式避免竞态条件更新票数
- 重定向到结果页面防止重复提交
结果页面实现
投票完成后,我们需要显示结果页面:
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>
使用通用视图简化代码
Django提供了通用视图来简化常见模式。我们的index、detail和results视图都可以用通用视图重写。
修改URL配置
首先更新URL配置:
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"),
]
注意URL参数名从question_id改为pk,这是通用视图的约定。
实现通用视图
然后重写视图为基于类的通用视图:
class IndexView(generic.ListView):
template_name = "polls/index.html"
context_object_name = "latest_question_list"
def get_queryset(self):
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"
这些通用视图自动处理了许多常见任务:
- ListView自动获取模型列表并传递给模板
- DetailView自动根据pk获取单个对象
- 我们可以通过template_name指定自定义模板路径
- 通过context_object_name自定义上下文变量名
总结
本部分教程我们学习了:
- 如何在Django中处理表单提交
- 使用POST方法修改服务器数据的最佳实践
- 实现投票功能并显示结果
- 使用Django通用视图简化代码
通用视图是Django强大的功能之一,可以显著减少重复代码。它们抽象了常见的Web开发模式,让我们可以专注于应用特有的逻辑。
在下一部分中,我们将学习如何测试这个投票应用,确保它的稳定性和可靠性。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考