===================================== 你的第一个 Django 应用程序, 第四部分 ===================================== 上接 `Tutorial 3`_ . 我们继续这个 Web-poll 应用程序并聚焦于表单处理及代码精简. 写一个简单的 form =================== 现在我们修改上一教程中创建的 poll detail 模板, 并让它包含一个 HTML ``
`` 元素::

{{ poll.question }}

{% if error_message %}

{{ error_message }}

{% endif %} {% for choice in poll.choice_set.all %}
{% endfor %}
纲要: * 以上模板为每个 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 }}

好了,在浏览器中访问 ``/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/