Django新手指南(7)

编写你的第一个Django程序,第四部分

 

本文继续第三部分讨论的内容。我们会继续开发网络投票程序并将深入研究简单的表单处理和缩减代码。

 

编写一个简单的表单

 

现在把上一部分中提到的polls/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>

 

总结一下:

上面的模板会给每个投票的答案选项设置一个单选框。每个单选框的值对应Choice对象的ID字段;名称对应Choice对象的choice字段。这就是说,当有人勾选了单选框并提交时,浏览器会提交POST数据choice=3。这就是HTML的基本法则。

 

现在创建个Django视图来处理提交的数据。在第三部分中,我们的URLconf设置有下面的内容:

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

 

现在我们在mysite/polls/views.py中创建vote()视图:

from django.shortcuts import get_object_or_404, render_to_response

from django.http import HttpResponseRedirect

from django.core.urlresolvers import reverse

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

        # Redisplay the poll voting form.

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

        # 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('mysite.polls.views.results', args=(p.id,)))

 

这段代码有些内容我们目前还没有提及到:

 

request.POST是一个类似于字典的对象,你可以用名称索引来引用提交的数据。这种情况下,request.POST['choice']会以字符串的形式返回被选中的投票选项的IDrequest.POST的值永远都是字符串。

同样在Django里,也可以用同样的方法从request.GET中获取GET数据。但是在这里我们使用request.POST,是因为为了保证数据是从POST方法提交过来的。

 

如果choicePOST数据中不存在,调用request.POST['choice']就会引发KeyError异常。上面的代码会检查是否触发KeyError异常,如果有异常就会重新显示提交表单和一段报错信息。

 

在增加了投票选项的统计数之后,最后返回了HttpResponseRedirect对象而不是HttpResponse对象。HttpResponseRedirect对象接收一个参数:跳转URL。(下面会说明怎么样构建URL

上面的注释里说明,你应该在POST数据处理完成之后永远返回HttpResponseRedirect对象。这个技巧不仅仅适用于Django,任何Web开发上都应该这么做。

 

在这个例子中我们在构造HttpResponseRedirect对象时使用了reverse()函数。这个函数可以解决硬编码URL产生的问题。我们只要传入要跳转的视图的名称和视图函数的参数就可以了。比如根据第三部分的URLconf设置,reverse()函数会返回下面的字符串:

'/polls/3/results/'

这里的3就是p.id的值。跳转后的URL会调用results视图函数并显示最终的页面。在这里你要使用视图函数的全名(包括前缀)。

 

在第三部分中提到,request是一个HttpRequest对象。要了解HttpRequest的更多内容,请参考文档request and response documentation

 

有人提交投票之后,vote()视图会跳转到对应的结果页面。现在来编写对应的视图:

def results(request, poll_id):

    p = get_object_or_404(Poll, pk=poll_id)

    return render_to_response('polls/results.html', {'poll': p})

 

这跟第三部分中的detail()视图基本上一模一样。唯一的区别就是模板名称。在后面我们会解决这个问题。

 

现在创建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/并完成投票,你就能看到结果页面了,而且每次投票后都能看到数据有更新。如果没有勾选任何选项的话,你会看到错误信息。

 

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

 

detail()results()都太简单了,而且代码上也有重复。显示投票列表的index()也有类似的问题。

 

这些视图都代表了Web开发中的一类现象:根据URL中的参数从数据库中获取数据,加载模板并返回渲染后的内容。这类现象非常普遍,因此Django提供了快捷方法,称为“通用视图”。

 

有了通用视图,你就不需要写任何Python代码来编写程序了。

 

现在用通用视图来修改投票程序,我们就可以删除掉一些代码了。下面只需要做几步就能完成修改。

 

为什么要梳理代码?

一般,编写Django程序时,你需要估计一下使用通用视图是否适合你的系统,如果适合,那从最开始就应该使用通用视图而不是开发了一半程序再来改代码。但是本文有意从一开始就介绍自行编写视图的方法,是为了让读者理解核心内容。

就好像你要使用计算机,至少应该知道基本的数学知识。

 

首先,打开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),

    url(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'),

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

)

 

我们这里用了两个通用视图:object_list()object_detail()。这两个视图分别用于“展示纪录的列表”和“展示某个特定的记录”。

l         每个通用视图都要知道需要使用哪些数据。这些数据在一个字典里提供。字典中的queryset索引对应的数据就是视图中要操作的对象。

l         object_detail()通用视图需要一个从URL中捕获的ID值,名称限定为object_id。所以我们把poll_id改成了object_id

l         results视图里我们加上了一个poll_results参数,这样就能在后面引用它的URL了(请看naming URL patterns文档)。这里还要使用django.conf.urls.defaultsurl()函数。在刚才这样的情景下使用url()函数是个很好的习惯。

 

一般,object_detail()通用视图使用<app name>/<model name>_detail.html作为模板。这里,该视图会使用polls/poll_detail.html。所以,把polls/detail.html更名为polls/poll_detail.html,然后在vote()中修改render_to_response()这一行。

 

同样,object_list()通用视图使用<app name>/<model name>_list.html作为模板。所以,把polls/index.html更名为polls/poll_list.html

 

由于在这个投票程序中的URLconf设置里,有多个纪录都用到了object_detail()视图,我们要人工给results视图指定模板名:template_name='polls/results.html'。否则,这些使用了同一个通用视图的URL就会加载同一个模板。注意这里我们用dict()来返回一个新的字典。

 

注意

django.db.models.QuerySet.all()是一个懒惰查询方法。

detail视图中只需要使用一条Poll纪录,而在这里使用Poll.objects.all()方法看起来让人觉得会影响性能。请别担心,Poll.objects.all()返回一个称为QuerySet的对象,实际上,这个对象只有在真正必要的时候才会去访问数据库。在查询数据库时,object_detail()视图会将范围缩减到单个纪录,所以只会从数据库返回被选中的纪录。

如果你想知道这其中到底是怎么工作的,Django 数据库API文档会告诉你QuerySet对象的懒惰特性

 

在前面几部分中,模板内都传入了一个包含polllatest_poll_listcontext对象。但是通用视图提供objectobject_list作为context对象。所以,需要修改模板文件来适应新的context变量。在你的模板中,将所有latest_poll_list替换为object_list,所有poll替换为object

 

你现在可以从polls/views.py中删除index()detail()result()视图了。我们不再需要这些代码了——它们已经被通用函数代替。

 

vote()视图还是必要的,但是还是要修改一下变量名。在调用render_to_response()时,将poll重命名为object

 

最后一件要做的事就是为了跳转到通用视图而修改一下URL的处理。在vote视图中,使用了reverse()来解决硬编码URL的问题。现在我们使用了通用视图,需要修改reverse()重新指向到通用视图。在reverse()中我们不能简单地再直接用视图的名称了——因为在URLconf中通用视图是可以多次使用的,这样就没办法分辨到底跳转到哪个URL——但是我们可以使用给定的名称:

return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))

 

重新启动测试服务器,看看这个使用了通用视图的全新程序。

 

要了解更多通用视图的内容,请参考通用视图文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值