[翻译]编写你的首个Django app, part 4

这份指南接着Tutorial 3继续讲解。我们将继续进行我们的Web-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>

一个简单的介绍:

  • 上面的模板为每一个question选项显示了一个radio button。每个radio button的值是其关联的question选项的ID。每个radio button的名字是"choice"。那即是说,当有人选择了radio buttons中的一个并提交了表单时,它将发送POST数据choice=#,其中#是选中的选项的ID。这是HTML表单的基本概念。
  • 我们把表单的action设置为{% url 'polls:vote' question.id %},并设置method="post"。使用method="post" (而不是想法使用method="get")是非常重要的,因为提交这个表单的行为将改变服务器端的数据。无论何时你创建了一个表单,其改变了服务器端的数据,请都使用method="post"。这个建议不仅仅适用于Django;它只是一个好的Web开发实践。
  • forloop.counter指示for标签执行了多少次循环体。
  • 由于我们在创建一个POST表单(它的执行可能会修改数据),我们需要考虑Cross Site Request Forgeries的问题。谢天谢地,你不需要为此太过担忧,因为Django有一个非常易于使用的系统来做保护。简单的说就是,目标为内部URLs的所有POST表单都应该使用{%csrf_token %}模板标签。

现在让我们创建一个Django view来处理提交的数据,并用它来做点事情。记住,在Tutorial 3中,我们为polls应用创建了一个URLconf,其包含这一行:

polls/urls.py
url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'),

我们也创建了一个vote()函数的dummy实现。让我们创建一个真实的版本。给polls/views.py添加如下的内容:

polls/views.py
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse

from polls.models import Choice, Question
# ...
def vote(request, question_id):
    p = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', {
            'question': p,
            '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=(p.id,)))

这份代码包含了一些在这份文档中我们之前没有提到过的东西:

  • request.POST是一个类字典对象,它使我们能够通过key名字来访问提交的数据。这个case里,request.POST['choice']以一个字符串的形式返回所选择的choice的ID。request.POST值总是字符串

    注意Django也提供了request.GET以相同的方式来访问GET数据 —— 但在我们的代码中我们显式地使用request.POST,来确保数据只通过一个POST调用改变。

  • 如果在POST数据中没有提供choice,request.POST['choice']将激起KeyError。上面的代码检查了KeyError,并在没有提供choice时以一条error消息重新显示question表单。

  • 在增加了choice计数之后,代码返回一个HttpResponseRedirect而不是一个普通的HttpResponseHttpResponseRedirect接收一个单独的参数:用户将被重定向到的URL(参考下面的说明来了解,我们在这个case中如何构造URL)。

    如同上面的Python注释中指出的,你应该总是在成功处理了POST数据之后返回一个HttpResponseRedirect。这个提示不特别用于Django;它只是一个很好的Web开发实践。

  • 这个例子中,我们在HttpResponseRedirect构造函数里使用了reverse()函数。这个函数帮助避免在view函数中硬编码一个URL。It is given the name of the view that we want to pass control to and the variable portion of the URL pattern that points to that view.在这个case中,使用了我们在Tutorial 3中设置的URLconf,这个reverse()调用会返回一个如下这样的字符串:

'/polls/3/results/'
  • ... 其中3是p.id的值。这个重定向的URL将在随后调用'results' view来显示最后的页面。

如同在Tutorial 3中提到的,request是一个HttpRequest对象。要获取更多关于HttpRequest对象的信息,请参考request and response documentation。

有人在一个question中投票之后,vote() view会将那个question重定向到results页面。让我们来编写那个view:

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})

这个函数与Tutorial 3中的detail() view几乎完全一样。仅有的不同就是模板的名字。我们将会在后面解决这个冗余。

现在,创建一个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/,并在question中投票。你应该看到了一个results页面,并且每次在你投票之后它都会得到更新。如果你提交了表单而没有选择一个choice,你应该看到error消息。

使用generic views:代码越少越好

detail() (来自与Tutorial 3)results() views都有点太简单了——而且如上面提到的,有些冗余。index() view (也来自于Tutorial 3),它显示一个polls的列表,也类似。

这些views展示了基本Web开发的一个常见场景:根据在URL中传进来的一个参数从数据库获得数据,加载一个模板,并返回rendered的模板。由于这是如此的常见,Django提供了一个快捷方式,称为“generic views”系统。

.Generic views抽象了通用的模式,使得你甚至不需要编写Python code就写出一个app。

让我们转换我们的poll app来使用generic views系统,我们将因此而能够删除大量我们自己的代码。我们只需要做到下面的几步就可以完成转换了。我们将:

  1. 转换URLconf。
  2. 删除一些老的,不需要的views。
  3. 引入新的基于Django的generic views的views。

Read on for details.


Why the code-shuffle?

通常,当你编写一个Django app时,你需要评估对于你要解决的问题而言,generic views是否是一个好方法,然后在一开始就使用它们,而不是做到了一半来重构。但这份指南直到现在一直聚焦于以“the hard way”编写views,来集中于核心概念。

在你开始使用一个计算器之前你应该先了解基本的数据知识。

修改URLconf

首先,打开polls/urls.py URLconf,然后像下面这样修改它:

polls/urls.py

from django.conf.urls import patterns, url

from polls import views

urlpatterns = patterns('',
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
    url(r'^(?P<pk>\d+)/results/$', views.ResultsView.as_view(), name='results'),
    url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'),
)

注意,第二和第三个模式的正则表达式匹配的模式的name已经由<question_id>变为了<pk>。

修改views

接下来,我们将移除我们老的index,detail和results views,并使用Django的generic views来替换。要做到这一点,打开polls/views.py文件,并像这样来修改它:

polls/views.py

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic

from polls.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

在这里我们使用了两个generic views:ListViewDetailView。那两个views分别抽象了“显示一个对象的列表”和“为一个特定类型的对象显示一个详细页面”的概念。

  • 每个generic view需要知道它的行为将作用于的模型(model)。这通过使用model属性来提供。
  • DetailView generic view期望从URL里面称为“pk”的参数中抓取主键值,因而我们为generic views将question_id改为pk。

默认情况下,DetailView generic view使用一个称为<app name>/<model name>_detail.html的模板。在我们的例子中,它将使用模板"polls/question_detail.html"。template_name属性被用于告诉Django,要使用一个特定的模板名字来代替自动生成的默认的模板名字。我们也为results list view指定了template_name – 这确保results view和detail view在rendered时有一个不同的外观,尽管在场景背后它们都是一个DetailView

类似地,ListView generic view使用了一个默认的称为<app name>/<model name>_list.html的模板;我们使用template_name告诉ListView来使用我们已有的"polls/index.html"模板。

在这份指南前面的部分,已经通过一个包含有question和latest_question_list context变量的context来提供了模板。对于DetailView,question变量是自动提供地 – 由于我们使用了一个Django model(Question),Django能够为context变量确定一个适当的名称。然而,对于ListView,自动生成的context变量是question_list。要覆写这一点,我们要提供context_object_name属性,指定我们想要用latest_question_list来替换。作为一个可选的方法,你可以修改你的templates来匹配新的默认的context变量 – 但是只告诉Django去使用你想使用的变量要简单得多。

运行服务器,并使用你的基于generic views的新polling app。

关于generic views的完整的细节,请参考generic views documentation。

当你熟悉了表单和generic views,就可以阅读part 5 of this tutorial,来学习测试我们的polls app的东西了。

原文地址:https://docs.djangoproject.com/en/1.7/intro/tutorial04/

你可能感兴趣的:([翻译]编写你的首个Django app, part 4)