实战Django:官方实例Part4

上一个part我们创建了投票的内容页,但这个页面仅仅局限于静态展示,投票的“投”字还无从体现。接下来,我们就来看一下,如何把票投起来。

 

19.创建表单



我们来更新模板文件polls/detail.html,在其中加入“form”元素:

polls/templates/polls/detail.html

<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="投票" />
</form>

简单讲解一下:

  • 在上面这个模板中,我们给每一个投票项加入一个单选按钮,每个单选按钮都关联到投票项的编号,单选按钮的名字统一叫做“choice”,这样当有用户选中一个投票项并提交表单(即投票)的时候,它就会发送一组”choice=#”(#为编号)的数据,这是html表单的基本概念。
  • 我们把表单的action设置为“{% url ‘polls:vote‘ question.id %}”,把它的method设置为“post”,“post”意味着让用户把这数据提交给服务器端。
  • forloop.counter显示了for循环的次数。
  • 当我们使用POST方式来提交表单的时候,我们需要考虑跨站请求伪造的风险。不过不用担心,Django早就为我们考虑到了这一点,你只要在模板里加上“ {% csrf_token %} ”标签,就可以规避风险。

注意,别忘了将模板文件存为utf-8编码。

接下来,我们修改相应的视图,编辑polls/views.py文件,改成下面这样:

polls/views.py:

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse

from polls.models import Choice, 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.DoesNotExist):
        # 没有选中投票项的,重新显示投票表单.
        return render(request, ‘polls/detail.html‘, {
            ‘question‘: p,
            ‘error_message‘: "You didn‘t 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‘]来获得已选择的投票项的编号。
  • 当用户提交投票时并没有选中任何投票项时,request.POST[‘choice‘]会抛出一个KeyError的异常,此时我们会重新显示投票表单。
  • 投票成功时,当前选中的投票项的票数会自动加1,此后我们会返回一个HttpResponseRedirect对象(而不是之前我们用过的HttpResponse),这个HttpResponseRedirect只带一个参数,就是用户投票后我们想让用户看到的页面链接。
  • 在HttpResponseRedirect中我们使用了reverse() 函数,我们用这个函数来生成上面讲到的页面链接,在本例中,这个reverse() 返回的字符会象下面这个样子:
‘/polls/3/results/‘

上面的3就来自于“p.id”这个参数,这个重定向链接会调用”results”视图来显示最终的页面。

当用户完成投票后,vote视图会将链接转到结果页,接下来,我们就来编写结果页的视图。

编辑polls/views.py文件:

polls/views.py:

from django.shortcuts import get_object_or_404, render

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

这个视图和我们前面介绍过的detail视图很象,只是模板名称不同而已。

然后我们给这个视图配上相应的模板。创建一个模板文件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 %}">再投一次?</a>

下面,让我们访问链接http://127.0.0.1:8000/polls/1/,并开始投票:

成功投票之后,我们可以看到页面自动跳转到结果页,在这一页我们可以看到投票的数据:

你还可以试一下什么都不选的时候,点击投票按钮会有什么结果。

20.使用通用视图,精简代码



在Web开发中,我们追求“Less code is better”的理念。在Part3中的detail视图和这里的results视图非常相似,但代码略显冗余,可以进行精简。同样可精简的还有我们的index视图。

Django给我们提供了通用视图,我们可以在polls这个应用中使用通用视图,来把代码改造得更加简洁。

要使用通用视图,我们需要做以下操作:

  • 转换url配置文件;
  • 删除一些不需要的视图;
  • 引入通用视图;

首先我们来调整url配置文件,编辑polls/urls.py文件,改成这样:

polls/urls.py:

from django.conf.urls import patterns, url

from polls import views

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

注意,url中的正则表达式发生了变化,第二、三个正则表达式中,我们把<question_id> 改成了<pk>。

下一步,我们把index,detail,results视图删除,换上Django的通用视图。编辑polls/views.py,把它改成下面这样:

polls/views.py:

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic

from polls.models import Choice, 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‘

我们使用了两个通用视图,ListView和DetailView。前一个视图用列表的方式来显示一组对象,后一个视图则用来显示某一特定对象的详细内容。

  • 每一个视图都需要知道它将对哪个模型进行操作,所以我们给它提供了model属性;
  • DetailView需要从链接中获取主键值(pk),这也就是我们在urls.py中把<question_id> 改成<pk>的原因。

如果不给DetailView指定template_name属性的话,它将使用<应用名称>/<模型名称>_detail.html作为模板名称,在本例中,这个默认的模板名会是这个样子:polls/question_detail.html。因为我们的detail视图和results视图都使用了DetailView,所以要给它们分别指定template_name,以便用不同的模板来呈现各自的内容。

与此类似的是,如果不给ListView指定template_name属性,它将使用<应用名称>/<模型名称>_list.html作为模板名称,我们给index视图指定了template_name属性,让它使用我们已经做好的”polls/index.html“模板。

在Part3中,我们的视图向模板传递了question和latest_question_list变量,在DeltailView中,我们不需要传递question变量,它会自动将其发给模板。但是,在ListView中,它向模板自动提交的变量是question_list——这是一个完整的列表,而我们只需要提供最新的5个就足够了。我们希望用latest_question_list来代替默认的question_list,因此我们给模板提供了context_object_name属性。

做完上面这些,让我们再次在浏览器中查看我们的工作成果。

在Part5中,舍得将讲述如何测试我们的投票应用。

 

【未完待续】

本文版权归舍得学苑所有,欢迎转载,转载请注明作者和出处。谢谢!

作者:舍得

首发:舍得学苑@博客园

时间: 2025-01-03 17:19:46

实战Django:官方实例Part4的相关文章

实战Django:官方实例Part1

[写在前面] 撰写这个实战系列的Django文章,是很久之前就有的想法,问题是手头实例太少,一旦开讲,恐有"无米下锅"之忧. 随着对Django学习的深入,渐渐有了些心得,把这些心得整理出来,以规范的.方便新人上手的撰写方式来写这个实战系列,相信对刚接触Django的童鞋会有一定的帮助. 舍得这里所用到的版本,为Django 1.7.1,Python 3.2,想要按照舍得讲解的实例上手练习的话,请务必与舍得所用的版本保持一致. 实例是在Windows系统下调试的,如果你采用的是非Win

实战Django:官方实例Part2

我们接着Part1部分往下讲.我们在part1中启动服务器后,并没有在管理页面中发现新添加的Polls应用,怎么办捏? 7.在管理界面中显示Question 只要注册一下这个应用就可以了.编辑polls/admin.py 文件,让它变成下面这个样子: polls/admin.py: from django.contrib import admin from polls.models import Question admin.site.register(Question) 这样Django就知道

实战Django:Rango Part4

在这个部分,我们将要开始学习如何设计用户系统,Django提供了一个叫"django.contrib.auth"的应用,我们可以用它来做出用户注册.登录.注销等一系列功能. 17.用户模型 在Django自带的用户系统中,用户模型里已经包含了如下数据: 用户名 密码 电子邮箱 姓名 我们想在用户模型中增加一些额外的字段,如: 链接字段:允许用户展示他们自己的个人网站 图片字段:让用户可以给自己设置一个头像 我们只要更新模型就可以实现上述的需求,编辑rango/models.py文件,先

实战Django:Rango Part2

  在这一节,我们将学习如何用自动脚本来添加内容,然后给我们的应用添加首页和一个内容页. 9.Part1练习答案 除了加入views和likes两个字段,这里我们需要思考一个问题,用户将如何来访问我们的页面?用/rango/category/1/ 或 /rango/category/2/?他们怎么知道1代表哪个分类,2又代表哪个分类?有没有更直接一点的方法呢? 我想还是直接用分类名称来作为链接的一部分会比较好一些,比如用/rango/category/Python/来访问Python分类这样子.

实战Django:官方实例Part6

我们终于迎来了官方实例的最后一个Part.在这一节中,舍得要向大家介绍Django的静态文件管理. 现在,我们要往这个投票应用里面添加一个CSS样式表和一张图片. 一个完整的网页文件,除了html文档外,通常还需要其它一些文件,比如图片.JS或者CSS,这些文件,我们称之为"静态文件". 在那些小项目中,我们不需要对静态文件关注太多,因为你可以将这些文件随便放置,只要你的服务器能查找得到就好.但在大一点的项目中,尤其是那些拥有好多个应用的项目,静态文件的管理便变得非常重要,所以骚年,还

实战Django:简易博客Part1

舍得学习新技能的时候,通常不喜欢傻读书--捧着一本阐述该项技能的书籍,然后傻看,一路看下来,脑子里塞满了新的概念.知识点,头是越来越大,但技能却几乎没掌握半分. 多年来,舍得养成了用做实例来学习新技能的习惯.这可不是舍得的独创,在象棋.围棋界中,历来有"打谱"一说,就是根据棋谱,把一步步棋摆出来,在打谱的过程中,去感悟.去吸收,此时感悟到的.吸收进的,才是属于你自己的东西.新的技能就这样一步步发展起来,逐渐壮大. 就象Django官方文档那样,一个实例看起来很简单,但作为初学者,用&q

实战Django:简易博客Part2

在Part1中,我们完成了本项目的基本框架搭建,并完善了一下管理后台,现在我们来看如何设计面向公共的页面部分. 8.创建模板 首先,请在blog文件夹下建立templates文件夹,然后,在templates文件夹中建立一个叫bog的文件夹,在这个bog文件夹下新建一个archive.html文件,这个文件的路径应该是: bog/templates/bog/archive.html 把下面这些代码添加到archive.html中: bog/templates/bog/archive.html:

实战Django:Pastebin

  这是<Django Web开发指南>中的最后一个实例.如果说上一个实例Liveblog重点讲的是Django和Ajax的协作,那么我们在Pastebin中,将学习到Django和高亮语法JS的协作,顺便复习一下在Django中加入表单. 1.创建项目和应用 我们先来创建本实例的项目,在dos命令提示符下转到Scripts文件夹(如"c:\python32\Scripts"),然后运行如下命令: $ django-admin startproject pastebinpr

实战Django:Rango Part1

在前面我们已经陆续学习了六个Django的实例,其中一个来自Django官方的文档,另外五个来自<Django Web开发指南>.舍得介绍的每一个实例,都是按照官方文档的标准,力求让大家在根据文章去做自己的实例时,能够按"文"索骥,轻松再现整个实例的制作过程. 接下来,舍得要介绍一个大型的实例,这个项目叫做Rango,它来自Django圈内口碑甚佳的<How to Tango with Django>一书.事实上,此书就是围绕Rango这一个实例来展开的,而且,