=====================================
你的第一个 Django 应用程序, 第四部分
=====================================
上接 `Tutorial 3`_ . 我们继续这个 Web-poll 应用程序并聚焦于表单处理及代码精简.
写一个简单的 form
===================
现在我们修改上一教程中创建的 poll detail 模板, 并让它包含一个 HTML ``
纲要:
* 以上模板为每个 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\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`` 模板::
{{ poll.question }}
{% for choice in poll.choice_set.all %}
- {{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
{% endfor %}
好了,在浏览器中访问 ``/polls/1/`` 页面并对该测验投票, 你会看到结果页在你每次投票之后更新. 如果你没有作出任何选择就提交表单,则会看到出错信息.
.. _request and response documentation: http://www.djangoproject.com/documentation/request_response/
使用 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 系统, 这样我们就能大大减少自己书写的代码.只需要很少的几步就可以完成这个改变:
.. admonition:: 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\d+)/$', 'detail'),
(r'^(?P\d+)/results/$', 'results'),
(r'^(?P\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\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
(r'^(?P\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html')),
(r'^(?P\d+)/vote/$', 'mysite.polls.views.vote'),
)
在这里我们使用耻两具 generic views : ``object_list`` 和 ``object_detail``.分别表示 "显示一个对象列表" 及 "显示一个对象的细节页".
* 每个 generic view 需要知道要处理哪些数据. 这些数据必须以一个字典的形式提供. 其中字典的 ``queryset`` 键指向要由 generic view 处理的对象列表.
* ``object_detail`` generic view 期望从URL中捕获一个 ID 值, 并且名字为 ``"object_id"``, 所以我们修改 ``poll_id`` 为
``object_id`` 以适应 generic views 的要求.
默认的, ``object_detail`` generic view 使用模板 ``/_detail.html``. 在我们这个例子里, 它会使用模板 ``"polls/poll_detail.html"``. 这样, 就要将你的 ``polls/detail.html`` 模板改名为 ``polls/poll_detail.html``, 并修改 ``vote()`` 中的 ``render_to_response()`` 行.
类似的, ``object_list`` generic view 使用名为 ``/_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`_.
.. _generic views documentation: http://www.djangoproject.com/documentation/generic_views/
即将推出
===========
目前教程到这儿就结束了. 希望不久以后你可以检出后面的部分:
* 高级表单处理
* 使用 RSS 框架
* 使用 cache 框架
* 使用评论 framework
* 高级管理特性: 权限处理
* 高级管理特性: 自定义 JavaScript
.. _Tutorial 3: http://www.djangoproject.com/documentation/tutorial3/