你的第一个 Django 应用程序, 第四部分

上接 Tutorial 3 . 我们继续这个 Web-poll 应用程序并聚焦于表单处理及代码精简.

写一个简单的 form

现在我们修改上一教程中创建的 poll detail 模板, 并让它包含一个 HTML <form> 元素:

<h1>{{ poll.question }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="/polls/{{ poll.id }}/vote/" method="post">
{% for choice in poll.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>

纲要:

  • 以上模板为每个 poll choice 提供一个单选钮. 每个单选钮的 value 是相关的choice的 ID. 每个单选钮的 name 都是 "choice". 也就是说, 当某人选中一个单选钮并提交表单时, 它会发送 POST data choice=3. This is HTML Forms 101.
  • 我们将表单的 action 设置为 /polls/{{ poll.id }}/vote/, 并设置 method="post". 使用 method="post" (对应的是 method="get")非常重要, 因为提交表单会改变服务器端数据. 无论何时你创建一个会导致服务器端数据改变的表单时,都应该使用 method="post". 这并不是 Django 的要求, 这是一个好的WEB开发习惯.(安全性佳)

现在我们写一个 Django view 来处理提交数据. 记住, 在 Tutorial 3 中, 我们为 polls 程序创建了一个 URLconf, 其中有这么一行:

(r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),

那我们就在 mysite/polls/views.py 中写这个 vote() 函数:

from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect
from mysite.polls.models import Choice, Poll
# ...
def vote(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # 重新显示投票表单.
        return render_to_response('polls/detail.html', {
            'poll': p,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # 在成功处理 POST 数据之后总是返回 HttpResponseRedirect 可以有效避免数据被提交两次
        # 或者用户点击 BACK 按钮.
        return HttpResponseRedirect('/polls/%s/results/' % p.id)

以上代码包含一些我们尚未在本教程中提及的东西:

  • request.POST 是一个类字典对象, 你可以通过键名访问提交数据. 也就是说 request.POST['choice'] 可以得到被选中的 choice id(字符串形式). request.POST 的值总是字符串.

    Django 也提供了 request.GET 以同样的方式存取 GET 数据 -- 不过我们在代码中显式的使用了 request.POST, 以确保只能通过 POST 调用来修改服务器数据.

  • 如果 POST 数据未提供 choice 键时, request.POST['choice'] 会抛出 KeyError 异常, 当未提供 choice 时以上代码会捕获 KeyError 异常并重新显示投票表单.

  • 在增加 choice 票数之后, 代码返回一个 HttpResponseRedirect 对象而不是 HttpResponse 对象. HttpResponseRedirect 需要一个参数: 用户即将重定向的 URL. 不要提供 "http://" 和域名. 这将有助于你的 app 更容易跨域移植.

    如同上面的 Python 代码注释指出的, 在成功处理 POST 数据之后,你应该总是返回一个 HttpResponseRedirect 对象.这并不是 Django 的要求, 这是一个好的WEB开发习惯.

在教程第三部分我们提到, request 是一个 HTTPRequest 对象. 要了解 HTTPRequest 对象更多,参阅 request and response documentation.

在某人投票之后, vote() view 自动将页面重定向到该测验的结果页, 我们现在来写结果页面的 view:

def results(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    return render_to_response('polls/results.html', {'poll': p})

这几乎与 Tutorial 3 中的 detail() view 完全相同. 唯一的不同就是模板的名字. 在后面我们会使用更好的手段更少的代码来做这两件事.

现在,创建一个 results.html 模板:

<h1>{{ poll.question }}</h1>

<ul>
{% for choice in poll.choice_set.all %}
    <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

好了,在浏览器中访问 /polls/1/ 页面并对该测验投票, 你会看到结果页在你每次投票之后更新. 如果你没有作出任何选择就提交表单,则会看到出错信息.

使用 generic views: 同样效果,代码更少

detail() (来自 Tutorial 3) 和 results() views 几乎雷同 -- 如上文所提到的, 甚至多作. index() view (来自 Tutorial 3), 显示 polls 列表, 也是如此.

这些 views 展示的是基本 WEB 开发的通用情况: 根据 URL 传递过来的参数从数据库中取出数据, 载入并渲染模板,然后展示给用户看. 因为这些操作实在太普遍了, Django 提供了一个捷径,这就是 "generic views" 系统.

Generic views 抽象了常见模式, 你不必书写 Python 代码就可以写一些 app.

现在我们来修改 poll app ,让它使用 generic views 系统, 这样我们就能大大减少自己书写的代码.只需要很少的几步就可以完成这个改变:

Why the code-shuffle?

通常, 当写一个 Django app 时, 你都应该评估你的问题是否适合使用 generic views, 你应该从头就开始使用它们,而不是中途再重构你的代码. 不过这个教程有意以"the hard way" 的方式书写 view , 现在才是聚焦于这些核心概念的时候.

当你准备用一个计算器时,你应该了解基本的算术知识.嘿嘿.

首先,打开 polls/urls.py URLconf. 它的内容就象下面这样:

from django.conf.urls.defaults import *

urlpatterns = patterns('mysite.polls.views',
    (r'^$', 'index'),
    (r'^(?P<poll_id>\d+)/$', 'detail'),
    (r'^(?P<poll_id>\d+)/results/$', 'results'),
    (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)

把它改成下面这样:

from django.conf.urls.defaults import *
from mysite.polls.models import Poll

info_dict = {
    'queryset': Poll.objects.all(),
}

urlpatterns = patterns('',
    (r'^$', 'django.views.generic.list_detail.object_list', info_dict),
    (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
    (r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html')),
    (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)

在这里我们使用耻两具 generic views : object_listobject_detail.分别表示 "显示一个对象列表" 及 "显示一个对象的细节页".

  • 每个 generic view 需要知道要处理哪些数据. 这些数据必须以一个字典的形式提供. 其中字典的 queryset 键指向要由 generic view 处理的对象列表.
  • object_detail generic view 期望从URL中捕获一个 ID 值, 并且名字为 "object_id", 所以我们修改 poll_idobject_id 以适应 generic views 的要求.

默认的, object_detail generic view 使用模板 <app name>/<module name>_detail.html. 在我们这个例子里, 它会使用模板 "polls/poll_detail.html". 这样, 就要将你的 polls/detail.html 模板改名为 polls/poll_detail.html, 并修改 vote() 中的 render_to_response() 行.

类似的, object_list generic view 使用名为 <app name>/<module name>_list.html 的模板. 这样就需要你将 poll/index.html 改名为 polls/poll_list.html.

由于在 poll app 中我们有不止一个URL入口使用 object_detail , 我们就为 results view 手工指定一个模板: template_name='polls/results.html'. 否则不同的 views 会使用同一个模板. 注意我们使用 dict() 来返回一个适当的字典.

现在你可以从你的 polls/views.py` 中删除 ``index(), detail()results() . 我们不再需要它们 -- 它们被 generic views 替换掉了.

不过 vote() view 仍然是必须的. 必须修改它以适当新的模板名及上下文变量名.在代码中将 polls/detail.html 改为 polls/poll_detail.html, 将 poll 上下文改名为 object .

运行web 服务器, 你的 poll app 现在基于 generic views 了.

要了解 generic views 的完整细节, 参阅 generic views documentation.

即将推出

目前教程到这儿就结束了. 希望不久以后你可以检出后面的部分:

  • 高级表单处理
  • 使用 RSS 框架
  • 使用 cache 框架
  • 使用评论 framework
  • 高级管理特性: 权限处理
  • 高级管理特性: 自定义 JavaScript