目录
No. 1 写一个简单的表单
让我们从Django 2.1.6.学习初级篇(3)中更新我们的poll detail模板(" polls/detail. HTML "),使模板包含一个HTML <form>元素:
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>
上面模板就是python和HTML语言的结合(是时候去了解一下html)。
我们将表单的重定向URL表达为action="{% url 'polls:vote' question.id %}",我们设置方法="post"。使用method="post"(相对于method="get")非常重要,因为提交此表单的行为将更改服务器端数据。无论何时创建更改服务器端数据的表单,请使用method="post"。这个技巧不是Django特有的;这只是一个好的Web开发实践。
forloop.counter指示for标记执行循环的次数
由于我们正在创建一个POST表单(它具有修改数据的效果),我们需要担心跨站点请求伪造(敲黑板,知识点)。幸运的是,您不必过于担心,因为Django提供了一个非常易于使用的系统来保护您免受其害。简而言之,所有针对内部url的POST表单都应该使用{% csrf_token %}模板标记。
现在,让我们创建一个Django视图来处理提交的数据并对其进行处理。请记住,在教程3中,我们为polls应用程序创建了一个URLconf,其中包括这一行:
polls/urls.py
path('<int:question_id>/vote/', views.vote, name='vote'),
向poll /view .py添加以下内容:
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 += 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['choice']:是一个类字典的对象,允许您通过键名访问提交的数据。在这种情况下,请求。POST['choice']以字符串形式返回所选选项的ID请求。POST值总是字符串。 请注意,Django还提供了GET请求,用于以相同方式访问GET数据,但我们使用的是request。在我们的代码中POST,以确保仅通过POST调用更改数据。
- 如果POST数据中没有提供选项,POST['choice']将引发KeyError。上面的代码检查KeyError并在没有给出选项的情况下重新显示带有错误消息的问题表单。
- 在else下面,代码返回一个HttpResponseRedirect,而不是一个普通的HttpResponse。HttpResponseRedirect只接受一个参数:用户将被重定向到的URL(在本例中,有关如何构造URL,请参见下面的要点)。
正如上面的Python注释所指出的,在成功处理POST数据之后,应该始终返回HttpResponseRedirect。这个技巧不是Django特有的;这只是一个好的Web开发实践。
在本例中,我们使用HttpResponseRedirect构造函数中的reverse()函数。这个函数有助于避免在视图函数中硬编码URL。它给出了我们想要传递控件的视图的名称以及指向该视图的URL模式的可变部分。在本例中,使用我们在初级篇(3)中设置的URLconf,这个reverse()调用将返回一个类似的字符串:
'/polls/3/results/'
其中3是question.id的值。这个重定向的URL将调用“results”视图来显示最终页面。
在某人对某个问题进行投票之后,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})
然后,编写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/并对问题进行投票。您应该会看到在每次投票时都会更新的结果页面。如果您提交表单而没有选择,您应该会看到错误消息(如果没有投票选项,那么你可以将模型中Choice迁移到后台管理,然后在后台增加你要投票的选项)。
值得注意的是:
我们的vote()视图的代码确实有一个小问题。它首先从数据库获取selected_choice对象,然后计算选票的新值,然后将其保存回数据库。如果您的网站的两个用户试图在完全相同的时间投票,这可能会出错:相同的值,假设是42,将被检索到用于投票。然后,对于两个用户,都计算并保存43的新值,但是44是期望值。
这叫做竞态条件,后续不提到如何解决。
No. 2 使用通用视图:代码越少越好
像index()这样的视图,根据URL中传递的参数从数据库获取数据,加载模板并返回呈现的模板。因为这很常见,Django提供了一个快捷方式,称为“通用视图”系统。
【通用视图】将常见模式抽象到甚至不需要编写Python代码就可以编写应用程序的程度。
让我们将polls投票应用程序转换为使用通用视图系统,这样我们就可以删除一些自己的代码。我们只需要采取一些步骤来进行转换。我们将:
1,更改URLconf
2,删除一些旧的、不需要的视图
3,根据Django的【通用视图】引入新的视图
--->修改URLconf
首先,更改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>。
--->修改views
接下来,我们将删除旧的索引、详细信息和结果视图,转而使用Django的通用视图。为此,请打开polls/views.py文件,并改变它如下:
polls/views.py
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.
这里我们使用两个通用视图:ListView和DetailView。这两个视图分别抽象了“显示对象列表”和“显示特定类型对象的详细页面”的概念。
每个通用视图都需要知道它将作用于什么模型。这是使用model属性提供的。
DetailView泛型视图期望从URL捕获的主键值被称为“pk”,因此我们将question_id更改为泛型视图的pk。
template_name属性用于告诉Django使用特定的模板名
对于DetailView,问题变量是自动提供的——因为我们使用的是Django模型(问题),Django能够为上下文变量确定适当的名称。但是,对于ListView(表示对象列表的页面,后文介绍,这里可以看做IndexView),自动生成的上下文变量是question_list。为了覆盖它,我们提供了context_object_name属性,指定要使用latest_question_list。作为一种替代方法,您可以更改模板以匹配新的默认上下文变量——但是Django会让你使用想要的变量变得容易。
运行服务器,并基于【通用视图】使用新的polls应用程序。有关【通用视图】系统的详细信息,后续介绍
当您熟悉表单和通用视图时,请阅读Django 2.1.6.学习初级篇(5),了解如何测试我们的polls应用程序