您的位置:首页 > 编程语言 > Python开发

Python教程之六-----编写你的第一个Django应用(4)

2017-09-11 09:53 567 查看
这个教程紧接(3)。我们将继续网页问卷调查应用并且集中讨论处理和砍掉我们代码的简单形式。

1. 编写一个简单的表单

让我们从上一个教程中升级我们问卷调查的明细模板('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>
快速纲要:

上述模板为每个问题选择显示了一个radio按钮。每个radio按钮的value关联到了问题选项的ID。每个radio按钮的name都是“choice”。那就是说,当某人选择其中一个选项并且提交了表单,它将发送POST数据choice=#,#表示被选择选项的ID。那就是HTML表单的基本理念。
我们将表单的action设置为{% url 'polls:vote' question.id %},并且我们设置了method='post'。使用method = 'post'(和method='get'是相反的)是非常重要的,因为提交这个表单的动作将会改变服务端的数据。不管何时你创建了一个改变服务端数据的表单,使用method='post'。Django没有指定这个提示;它只是一个好的Web开发经验。

forloop.counter表明for标签循环了多少次
因为我们创建了一个post表单(可以修改数据),我们需要考虑跨站点请求。感谢的,你不需要太担心,因为Django有一个非常简单易用的系统来防止它。简介的说,所有指定内部URLs的POST表单都应该使用{% csrf_token %}模板标签。
现在,让我们创建一个Django视图来处理提交的数据并做一些事。记住,在上一个教程中,我们为polls应用程序创建了一个包含下述行的URLconf:
polls/urls.py
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),

我们也创建了一个假的vote()函数的实现。让我们来创建一个真的。将下列添加到polls/views.py:
polls/views.py
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是一个类似于字典的对象,让你通过key名称来访问提交的数据。在这种情况下,request.POST['choice']返回了选择选项的ID,作为一个string。request.POST的值总是字符串。注意,Django同样提供request.GET以同样的方式来访问GET数据 -- 但我们明确的在我们的代码中使用request.POST,来确保数据只在通过POST调用的情况下被改变。
如果choice没有在POST数据中提供, request.POST['choice']将会抛出一个KeyError。如果choice没有指定,上述代码检查KeyError并且重新显示带有一个错误消息的问题表单。
当choice的计数增长后,代码返回一个HttpResponseReirect而不是一个正常的HttpResponse。HttpResponseRedirect只需要一个参数:用户将会被重定向的目的URL(阅读接下来的节点关于我们如何在这种情况下组成URL。)                                                                              
                                                                  就像上面注释指出的那样,你应该在当成功处理了POST数据之后,总是返回一个HttpResponseRedirect。这不是特别针对Django的,这是一个好的web开发习惯。
我们在这个例子的HttpResponseRedirect结构中使用reverse()函数。这个函数帮助避免在视图函数中的URL硬代码。它被传递了我们想要传递的视图的名称和指向视图的URL模式的可变部分。在这个例子中,使用我们在教程(3)里设置的URLconf,这个reverse()调用将返回一个字符串类似于:
'/polls/3/results/'
其中3是question.id的值.这个重定向URL将会调用'result'视图来显示最终的页面
正如上一教程提到的一样,request是一个HttpRequest对象,要了解更多关于HttpRequest对象,请阅读<请求和响应>文档。

当某人在一个问题中投票时,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()视图一样。唯一的不同是模板的名字。我们将在稍后修正这个冗余。

现在,创建一个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/在你的浏览器中,并且对问题进行投票。你将会看到一个每次你投完票都会更新的一个结果页面。如果你没有选择就提交了表单,你将会看到一个错误消息。

注意

我们vote()视图的代码有一个小问题。它首先从数据库中取得selected_choice对象,然后计算votes的新值,然后将它保存回数据库。如果你站点的2个用户尝试在几乎同一时间投票,这将会有问题:同样的值,例如42,将会被作为votes检索到。然后,2个用户都会计算并保存新值43,但44应该才是正确的。

这被称为竞争条件。如果你敢兴趣,你可以阅读<使用F()来避免竞争条件>来了解如何解决这个问题。


2. 使用通用视图:代码越少越好.

 detail()(来自于上一教程)和results()视图都是非常简单 -- 并且,如上面提到的,冗余。index()视图,显示一个问卷调查列表,也同样。

这些视图代表了基本web开发的一个通用情况:根据URL中传递的参数从数据库获取数据,加载一个模板并且返回渲染后的模板。因为这太通用了,Django提供了一个缩写,叫做‘通用视图’系统。

通用视图抽象通用模式到了这个地步,你甚至不需要写Python代码来编写一个应用。

让我们将我们的调查问卷应用转换成使用通用视图系统,所以我们能删除大量的代码。我们仅仅需要几步来完成这个转换。我们将:

转换URLconf
删除一些旧的,不需要的视图。
引进基于Django通用视图的新视图。
阅读明细。

为什么重新编写代码?

通常,当编写一个Django应用时,你会评估是否通用视图是符合你问题的好的选择,你会从开始就是用它们,而不是半路重构。但是这个教程直到现在都有意的将焦点关注在以“困难的方式”编写视图,来引出更多核心的概念。

你应该在使用计算机前直到基础的算术。

2.1 修正URLconf

首先,打开polls/urls.pyURLconf然后如下面修改它:
polls/urls.py
from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

注意,匹配部分的正则表达式的第二和第三部分已经从<question_id>修改成了<pk>。

2.2 修改视图

下一步,我们准备移除旧的index,detail和results视图,并且使用Django通用视图来代替。打开polls/views.py并且这样修改: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.
我们将使用2个通用视图:ListView和DetailView。各自的,这2个视图抽象了“显示对象列表”和“显示对象特殊类型的详细页面”的概念。

每一个通用视图需要了解它将在什么视图上执行。这使用model属性来提供。
DetailView通用视图期望从URL中捕获的被称为‘pk’的主键值,所以我们为通用视图将question_id修改成pk。
默认的,DetailView通用视图使用了一个称为<app name>/<model name>_detail.html的模板。在我们的情况中,它将使用模板'polls/question_detail.html'。template_name属性用于告诉Django去使用指定的模板名称而不是自动生成的默认模板名称。我们同样为results列表视图指定template_name -- 这确保了results视图和detail视图当渲染的时候会有个不同的表现,即使在幕后他们都是一个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你想要的变量将会更简单。

运行服务,使用你新的机遇通用视图的问卷调查应用。

关于通用视图更多的信息,请阅读<通用视图文档>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: