开始Django之旅-part4_使用表格简化你的代码
前一篇:开始Django之旅-part3_Django和view
后一篇:开始Django之旅-part5_自动测试你的代码
前言
这篇文章来告诉你,如何使用表格来简化代码。
写一个表格
我们更新一些上节课讲过的(‘polls/detail.html’)模板,让它包含<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>
快速的了解一下:
- 上面的模板为每个问题的选项设置了一个单选按钮,它们的id和choice.id关联,名字都是choice。这就说明,当有人选中某个单选按钮然后提交表格,它就会发送POST数据choice=#(#表示被选中按钮的id)。这是基本的HTML表格概念。
- 我们设置表格的action为 {% url ‘polls:vote’ question.id %},method为post。使用post方法很重要,因为提交表格会改变服务器端的数据。无论什么时候你创建了一个修改服务器端数据的表格,都要使用method=post。这在web开发中是比较常见的操作。
- forloop.counter表明for标签进行了多少次循环。
- 因为我们建立了一个post表格,它可以修改数据,我们可能会担心其他站点的伪造的请求。幸运的是,你不需要太担心,因为django结合了一个保护系统。
就是说,所有针对内部的URL表单都应该使用 {% csrf_token %}标签。
现在,我们开始创建一个处理提交过来的数据并实施确定操作的view。记住,上一篇我们已经创建了polls的URL配置文件,它包括了:
path('<int:question_id>/vote/', views.vote, name='vote'),
我们需要实现vote()方法:
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是一个类型字典的对象,可以让你访问通过关键字name访问提交过来的数据。在这个例子中,request.POST[‘choice’]返回了被选择的选项的id,把它当做了一个字符串。request.POST的值总是字符串。
注意:Django也提供了request.GET方法以同样的方式访问GET数据。
- request.POST[‘choice’]如果提供的post数据中choice不存在就会引发KeyError,然后重新展示question表格和错误信息。
- 在增加选择次数之后,代码返回HttpResponseRedirect对象而不是HttpResponse,HttpResponseRedirect只携带了一个参数:一个用户将要被重定向的地址。
上面的python注释指出:当成功的处理POST数据之后都应该返回一个HttpResponseRedirect对象.
- 我们在HttpResponseRedirect构造方法中使用了reverse()方法,它有助于避免在view中使用固定的URL。它给出了我们想要把控制权交给某个view的名字,和指向该view的URL的变量部分。在这个例子中,reverse()会范围一个字符串:
'/polls/3/results/'
3是question.id的值,该重定向的URL将会调用‘results’view,展示在最终页面上。
在上一篇中讲到,request是一个HttpRequest对象,关于更多,请看:request and response documentation.
在有人给问题投票后,vote() view就会重定向到question的结果页面,看下面的view:
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()方法非常类似,只是他们的模块名字不同,我们一会再调整这种冗余情况。
现在,创建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/问题下面投票,你投票点击后,应该会看到一个更新的结果。如果你没有选择选项就投票就会看到一个错误页面。
注意:view中的vote()方法还有一点小问题。它首先从数据库中获取selected_choiceduixiang ,然后计算votes的新值。如果两个用户在同一时间投票,可能会出错:加入数据库中存储的是42,对于两个个同时投票的用户都会得到43的结果,但是实际上应该是44。
这被称为紊乱,如果你有兴趣,你可以阅读
使用F()来避免紊乱
通用的view:代码更少
上一篇说提到的detail()和刚刚提到的results()都非常简短,并且冗余。index()也是类似的。
这些views体现了web开发的一个共同的情况:通过URL根据参数从数据库中获取数据,加载一个模板,然后返回已渲染的模板。因为这台平常了,所以Django提供了一个更简洁的称为‘通用view’系统。
通用的view抽象了一般的模式,你写app甚至不需要写python代码。
让我们的投票app开始使用通用view系统,所以我们要删除自己的代码,执行下面的步骤:
- 修改URLconf
- 删除旧的,不需要的views
- 引进基于Django通用的view。
一般来讲,当我们写Django app时,你会评估是否通用views适合你的问题,你应该一开始就使用它们,而不是半路使用它们重构你的代码。
Amend URLconf
首先,打开polls/urls.py URL配置文件,修改如下:
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.
Amend views
下一步,我们将要移除index,detail,results view,然后使用Django通用views代替。修改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.
这里我们使用了两个通用view:ListView和DetailView。对应的,它们两个view抽象了‘显示对象列表’和‘显示某一个对象的详情’的概念。
- 每个通用view需要去知道它操作的模型是什么,所以使用了model参数。
- DetailView期待重URL捕获到一个主键值(pk),所以我们使用pk替换了question_id。
默认情况下,DetailView使用一个叫做<app name>/<model name>_detail.html
,在我们的例子中,它会使用"polls/question_detail.html"这个模板。template_name 参数用于告诉Django使用一个具体的名字代替默认的模板名称。我们也可以具体或results的模板名称。
同样地,ListView通用view使用了一个默认的模板叫做<app name>/<model name>_list.html
,我们使用template_name告诉ListView使用我们存在的“polls/index.html”模板。
运行服务器,查看一下效果吧。
前一篇:开始Django之旅-part3_Django和view
后一篇:开始Django之旅-part5_自动测试你的代码