Walk by faith code, hack, curious

编写第一个Django程序(4)

编写第一个Django程序(3)
ok,我们接着之前的第3篇,开始我们的最后一篇.

创建一个简单的表单

我们修改一个测试页面的模板(polls/detail.html).

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

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

<form action="/polls/{{ poll.id }}/vote/" method="post">
{% csrf_token %}
{% 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>

简单说明一下:

1.上面的模板用单选框来展示一个民意测试的所有投票项.它的值就是对应的ID,他的名称就是对应选项的名称,他的作用就是当选中一个选项,然后提交表单,他将会传送一个post数据,choice=3

2. 我们把这个表单的action写作’ /polls/{{ poll.id }}/vote/‘,方法写为post.

3. forloop.counter指明循环多少次.

4. 既然我们创建了一个提交数据的表单,我们就需要关心注意 一下Cross site request forgeries.不错的是,你不需要担心怎么实现.Django已经内建通过支持了.简单的说,就是所有的post表单都指向到内部的一个地址,我们使用{% csrf_token%}标签完成.

这个{% csrf_token %}标签要从请求对象中获取信息,在模板的内容中我们不能获取它.我们需要在detail的view方法中做一个小的改动:

from django.template import RequestContext
# ...
def detail(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    return render_to_response('polls/detail.html', {'poll': p},
                               context_instance=RequestContext(request))

详细的说明请参考:https://docs.djangoproject.com/en/1.3/ref/templates/api/#subclassing-context-requestcontext

现在,我们创建view方法来处理提交过来的请求数据,在第三节中我们有这样一句:

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

下面来修改vote函数来干点实事:

from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.template import RequestContext
from 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.",
        }, context_instance=RequestContext(request))
    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.views.results', args=(p.id,)))

在这里做点说明:

1,request.POST相当于是一个字典的对象来让我们访问请求传递过来的数据通过key.在这个时候我们就能够通过request.POST[‘choice’]来获取选中项的ID,他的类型始终会是string.

同时,Django也提供了reques.GET来获取get方式传递过来的值,这里我们显式调用POST方法来确保获取的值是通过post方式过来的.

2,request.POST[‘choice’]将会在没有取到对应的值的时候报错.上面的代码检查了这个KeyError,然后会在没有选中选项的情况下返回页面显示错误信息.

3,在增加了投票数之后会重定向到结果页面.我们使用了HttpResponseRedirect来代替HttpResponse.HttpResponseRedirect有一个参数,就是将要重定向的地址.

为什么我们使用重定向呢?不仅仅是python要求这么做,而是作为web开发都是必须的.

4,我们使用了reverse()函数在HttpResponseRedirect参数里,这个函数能够帮助避免硬编码地址在视图的函数中.我们给的视图的名称我们希望传递到对应的控制器,url模式里的变量部分指向的就是视图,这个时候我们的例子中的urlconf中的配置,我们 会把它reverse到/polls/3/results/

关于更多的HttpRequest请参考:https://docs.djangoproject.com/en/1.3/ref/request-response/

 

在投票结束后会被重定向到results视图,也就是会到views里的results函数.编写它:

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>

<a href="/polls/{{ poll.id }}/">Vote again?</a>

到这里基本上就算是结束了整个一个过程.你可以去投票看看效果了!

不过我们还是要有点修改:

使用一些基本的视图来让代码变得更加简洁.

现在我们的detail页面和results页面都是傻到丑的地步,还有就是有点重复,首页也是展现了一个调查的列表.

这些都是一种基本的web开发的情形:通过链接传过来的参数然后从数据库去取数据,然后加载一个模板然后把取得的数据展现在模板上返回显示.因为这个实在是太常见了.所以Django内建了一个叫做’generic views’系统.

Generic views(我这里称作是共通视图)能够把视图中那些共通的部分给抽出来,然后就不需要再去编码了.让我们来把现有的程序添加上共通视图吧.只需一小步哦!

1.重写URLconf

2.删除旧的不用的views

3.给新的views添加URL处理

首先.打开polls/urls.py,修改成下面的这样:

from django.conf.urls.defaults import *
from django.views.generic import DetailView, ListView
from polls.models import Poll

urlpatterns = patterns('',
    url(r'^$',
        ListView.as_view(
            queryset=Poll.objects.order_by('-pub_date')[:5],
            context_object_name='latest_poll_list',
            template_name='polls/index.html')),
    url(r'^(?P<pk>\d+)/$',
        DetailView.as_view(
            model=Poll,
            template_name='polls/detail.html')),
    url(r'^(?P<pk>\d+)/results/$',
        DetailView.as_view(
            model=Poll,
            template_name='polls/results.html'),
        name='poll_results'),
    url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
)

我们用到了2个共通的视图.ListView和DetailView.ListView视图就是显示一个对象list.DetailView就是显示一个对象的详细信息.
1.每一个共通视图都需要知道显示什么对象.所以需要指明.通过model参数.
2.详细的共通视图需要有一个主键所以我们指定poll_id为pk.
3.我们添加了一个’poll_results’的结果视图到了vote()里的重定向目标.

默认的,详细视图需要一个模板称作/_detail.html.在我们的例子中,我们的模板名称是’polls/poll_detail.html’,这个文件名是用来告诉django我在使用特定的模板名来代替自动生成的默认模板文件名.
我们同样需要给results视图指定特定的文件名.这样做是为了确保这两个视图拥有不一样的外观,即使他们使用的都是详细视图.

同样,列表视图也是用一个默认的命名<app name>/<model name>_list.html.我们通过文件名来告诉列表视图来使用polls/index.html模板.

在之前的章节中我们提到的模板都是包含poll或者latest_poll_lsit等变量的.在详细的视图中poll对象是被自动提供的.-即我们程序中的对象Poll.Django能够确定当前视图中当前上下文中用到的具体的对象.可是在list视图中,自动生成的变量名就是poll_list了.我们能够通过context_object_name选项来重写这个变量,也就你想用的latest_poll_list变量了.

 

现在你就能够删掉index(),detail()和results()函数了.我们不在需要他们.他们的功能已经在共通视图中 搞定了.最后一件事情就是搞定URL映射.在上面的投票视图中,我们需要到reverse()函数来避免硬编码.现在我们转到了共通视图.我们需要修改reverse()方法来指向到一个新的视图.把上面的vote()方法中的reverse()中的参数改一下:

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

至此,全部已经结束,运行程序,体验一下吧~
有关于更多的关于Generic views的知识,请访问:https://docs.djangoproject.com/en/1.3/topics/http/generic-views/