民意调查Django实现(四)

我们接着上一小节的末尾开始学习,在上一个小节中,我们主要是了解了Django中的模板,即templates的使用。在这个小节中,我们主要关注于简单的表单处理并且裁剪我们的代码。

编写一个简单的表单

我们来更新一下我们的detail模板(“polls/detail.html”),添加HTML的<form>元素。

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

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

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }} "/>
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label></br>
{% endfor %}

<input type="submit" value="vote" />

</form>

简单描述一下:
- 上面的模板为每一个question展示了一组单选按钮。每一个按钮的值(value)是相关问题的选项ID。每一个按钮的名称是”choice”。也就是意味着,当用户选择其中一个选项框并且提交表单的时候,将会发送post数据choice=#,其中#就是对应的选择的选项的ID。这个是HTML表单的基本概念。
- 我们设置表单的action为{% url 'polls:vote' question.id %},并且我们设置method="post"。使用post方式(和使用get方式对应)是非常重要的,因为提交表单的行为将会改变服务端的数据。无论你什么时候创建能够修改服务端数据的表单的时候,一定要使用methon="post"。这个技巧不是Django专有的。这个仅仅就是好的网页开发技巧。
- forloop.counter标示for标签在这个循环中出现的次数。
- 既然我们创建了一个POST表单(该表单可能会影响修改数据),我们就需要去担心通过站点请求的伪装。幸运的是,我们不必太过担心,因为Django提供了一个简单易用的系统来阻止他。简单来说,目标是内部URLs的所有的POST表单都应该使用{% crsf_token %}模板标签。

现在,我们来创建处理提交数据的视图。在上一个小节中,我们创建了包含下面这一行的URLconf。

polls/urls.py

url(r'^(?P<question_id>\d+)/vote/$',views.vote,name='vote'),

我们也创造了一个vote()函数的临时实现。现在我们来真正使用起来。向polls/views.py中添加下面的代码:

# -*- coding:utf-8 -*-
from django.http import HttpResponse,HttpResponseRedirect
from django.shortcuts import get_object_or_404,render
from django.core.urlresolvers import reverse

from polls.models import Question

....

def vote(request,question_id):
    p = get_object_or_404(Question,pk=question_id)

    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExit):
        return render(request,'polls/detail.html',{
            'question':p,
            'errot_message':"You did not select a choice."
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # 每次成功处理POST数据之后一定要返回HttpResponseRedirect。
        # 这个可以避免二次提交
        return HttpResponseRedirect(reverse('polls/results',args=(p.id)))

该代码包含下面几个事情:

  • request.POST是一个类似于字典的对象,能够使我们通过键名称去访问提交的数据。在这个例子中,request.POST['choice']返回的是选中的选项的ID,这里是字符串。request.POST值一直是字符串。
    注意,Django也提供了request.GET去访问GET数据的方式,但是对于request.POST来说只有在代码中才能获取到相应的数据,这个大家可以去了解get方式请求和post方式请求的区别。

  • 如果在POST数据中没有提供choice选项,那么request.POST['choice']将会抛出KeyError异常。上面的代码检查KeyError异常,如果没有提供choice的话,重新显示带有错误信息的question表单。

  • 在增加了选项数量之后,代码返回一个HttpResponseRedirect而不是一个正常的HttpResponse。HttpResponseRedirect使用一个单一的参数:也就是用户将要重定向的url地址。
  • 在这个例子的HttpResponseRedirect结构中我们使用reverse()函数。这个函数帮助我们避免发生视图函数中硬编码一个URL地址。他被给予我们想要传递到的视图名称和指向视图的URL正则中的变量部分。在这个例子中,使用我们在上一小节中设置的URLconf,最终reverse()调用将会返回类似于下面的字符串:'/polls/3/results',其中的3是p.id的值。这个重定向URL然后调用’results’视图去展示最终的页面。

在用户对相应问题投票之后,vote()视图将会重定向到这个问题的results页面。现在我们来重写这个页面:

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})

下面我们来添加results模板:

创建一个polls/results.html模板:

polls/templates/polls/results.html

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

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

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

现在,我们在浏览器中进入/polls/1/中去为相应的问题去投票。每次投票后你都能看到相应的结果更新。如果你没有选择选项而点击提交的话,你将会看到错误信息。

下面来看一下我的运行结果:

这里写图片描述

这里写图片描述


使用通用视图:减少代码更好

detail()和results()视图是非常简单的 - 就像是上面提到的,太冗余了。展示投票列表的index()视图也是一样的。

这些视图代表着基本Web开发的通用例子:通过URL传来的参数从数据库获取数据,加载模板并且返回一个渲染模板。因为这些都是雷同的,所以Django提供了一个快捷方式,成为”通用视图”系统。

通用试图系统抽象除了通用正则,这使得我们可能不需要编写Python代码也能构件一个应用。

下面让我们的应用使用通用视图,所以我们可以删除一大些代码了。我们仅仅需要几步就能做出转换。

  1. 转换URLconf
  2. 删除一些旧的,不需要的视图
  3. 基于Django的通用视图引入新的视图

修改URLconf

首先,打开polls/urls.py并且像下面这样修改:

from django.conf.urls import patterns,url

from polls import views

urlpatterns = patterns('',
    # ex: /polls/
    url(r'^$', views.IndexView.as_view(),name='index'),
    # ex: /polls/5/
    url(r'^(?P<pk>\d+)/$',views.DetailView.as_view(),name='detail'),
    # ex: /polls/5/results
    url(r'^(?P<pk>\d+)/results/$',views.ResultsView.as_view(),name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>\d+)/vote/$',views.vote,name='vote'),
)

注意,正则匹配中间的<question_id>改成了<pk>


修改视图

下一步,我们将要移除我们旧的index,detail和results视图,然后使用Django的通用视图。打开polls/view.py文件,并且像下面这样修改:

# -*- coding:utf-8 -*-
from django.http import HttpResponse,HttpResponseRedirect
from django.shortcuts import get_object_or_404,render
from django.core.urlresolvers import reverse
from django.views import generic

from polls.models import Question


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]

class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'

class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'

def vote(request, question_id):
    # 和原来一样

我们使用了两个通用视图:ListView和DetailView。这两个视图抽象除了“显示对象列表”和“现象特定对象详细信息”的概念。

  • 每一个通用视图都需要知道他们操作的模型是什么。这个通过model属性提供
  • DetailView通用视图需要从URL中获取称为”pk”的主键,这就是为什么刚刚在urls.py中修改question_idpk的原因了。

默认情况下,DetailView通用视图使用称为<app name>/<model name>_detail.html作为模板。在我们的例子中,他会使用模板”polls/question_detail.html”。属性template_name被用来告诉Django使用能够哪一个模板代替默认模板。我们也为results指定了template_name。

相似的,ListView通用视图使用默认的模板,称为<app name>/<model name>_list.html,我们同样为他指定了我们自己的模板。

在之前的小节中,模板已经被提供了context,也就是上下文,该上下文包含question和latest_question_list变量。对于DetailView,question已经被自动提供了。我们使用Django的模型(Question),所以Django能够决定为上下文变量使用一个近似的名称。然而,对于ListView,自动生成的上下文变量是question_list。为了重写,我们可以使用context_object_name属性,指定我们想要使用的latest_question_list。这样的话,通过Django通用视图提供的属性,我们就可以指定我们想使用的变量了。

下面我们来运行一下,看一下效果:

运行效果和上面的。

posted @ 2015-12-16 16:19  陈洪波  阅读(187)  评论(0编辑  收藏  举报