本教程上接教程3,我们将继续开发网页投票应用,本部分将主要关注简单的表单处理以及如何对代码进行优化

写一个简单的表单

让我们更新一下在上一个教程中编写的投票详细页面的模板(‘polls/detail.html’),让它包含一个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="Vote" />
</form>

简要说明:

在detail网页模板中,我们为Question对应的每个Choice都添加了一个单选按钮用于选择。每个单选按钮的value属性是对应的各个Choice的ID。每个单选按钮的name是‘choice’。这意味着,当有人选择一个单选按钮并提交表单提交时,它将发送一个POST数据choice=#,其中#为选择的Choice的ID。这是HTML表单的基本概念。

我们设置表单的action为{% url 'polls:vote' question.id %},并设置method='post',使用method='post'(与其相对的是method='get')是非常重要的,因为这个提交表单的行为会改变服务器端的数据。无论何时创建一个更改数据服务端的表单,请使用method='post'。这个提示不是django的特有的,这只是很好的web开发实践

forloop.counter指示for标签已经循环多少次。

由于我们创建一个POST表单(它具有修改数据的作用),所以我们需要小心跨站点请求伪造。谢天谢地,你不必太过担心,因为django已经拥有一个用来防御它的非常容易使用的系统。简而言之,所有针对内部URL的POST表单都应该使用{% csrf_token %}模板标签

现在,我们来创建一个处理提交的数据的django视图,并用它来处理。记住,在教程3中,我们为投票应用创建了一个URLconf,包含这一行:

polls/urls.py

url(r'^(?P<question_id>[0-9]+)/vote/$',views.vote,name='vote')

我们还创建了一个vote()函数的模拟实现。让我们来创建一个真实的版本。将下面的代码添加到polls/views.py

polls/views.py

from django.shortcuts import get_object_or_404,render
from django.http import HttpResponseRedirect,HttpResponse
from django.urls import reverse from .models import Choice,Question def vote(request,question_id):
question=get_object_or_404(Question,pk=question_id)
try:
selected_choice=question.choice_set.get(pk=request.POST['choice'])
except (KeyError,Choice.DoesNotExist):
return render(request,'polls/detail.html',{'question':question,
'error_message':"you didn't select a choice"})
else:
selected_choice.votes+=1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:results',args=(question.id,)))

有些新的东西我们来解释一下:

request.POST是一个类似字典的对象,让你可以通过关键字的名字获取提交的数据。这个例子中,request.POST['choice']以字符串形式返回选择的Choice的ID。request.POST的值永远是字符串

注意,django还以同样的方式提供request.GET用于访问GET数据——但我们在代码中显式地使用request.POST,以保证数据只能通过POST调用改动。

如果在POST数据中没有提供request.POST['choice'],choice将引发一个keyError。上面的代码检查KeyError,如果没有给出choice将重新显示Question表单和一个错误信息

在增加Choice的得票数之后,代码返回一个HttpResponseRedirect而不是常用的HttpResponse。HttpResponseRedirct只接受一个参数:用户将要被重定向的URL(请继续看下去,我们将会解释如何构造这个例子中的URL)

正如上面的python注释指出的,你应该在成功处理POST数据后总是返回一个HttpResponseRedirct。这个提示不是django特有的;这只是很好的web开发实践

在这个例子中,我们在HttpResponseRedirct的构造函数中使用reverse()函数。这个函数避免了我们在视图函数中硬编码URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的URL模式中需要给该视图提供的参数。在这种情况下,使用我们在教程3中设置的URLconf,这个reverse()调用将返回一个字符串

'/polls/3/results/'

其中3是question.id的值。重定向的URL将调用‘results’视图来显示最终的页面

如教程3中所述,request是一个HttpRequest对象。更多关于HttpRequest对象的内容,请参见request and response文档

当有人对Question进行投票后,vote()视图将请求重定向到Question的结果页面。让我们来编写这个视图

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

这和教程3中的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 %}">Vote again?</a>

现在,在你的浏览器中访问/polls/1/然后为Question投票。你应该看到一个投票结果页面,并且在你每次投票之后都会更新。如果你提交时没有选择任何Choice,你应该看到错误信息

我们的vote()视图代码确实有一个小问题,它首先从数据库获取selected_choice对象,然后计算votes的新值,然后将其保存回数据库。如果您的网站的两个用户尝试在完全相同的时间进行投票,则可能会出错:对于votes,将检索相同的值,就是42。然后,对于两个用户,计算并保存新值43,但是44将是预期值

使用通用视图:较少的代码更好

detail()(在教程3中)和results()视图都很简单--并且,像上面提到的那样,存在冗余问题。显示轮询列表的index()视图是类似的

这些视图反映基本的web开发中的一个常见情况:根据url中的参数从数据库中获取数据、载入模板文件然后返回渲染后的模板。由于这种情况非常普遍,django提供了一种叫做‘generic views(通用视图)’的系统可以方便地进行处理

generic views会将常见的模式抽象化,可以使你在编写app时甚至不需要编写python代码

让我们将我们的投票应用转换成使用通用视图系统,这样我们可以删除许多我们的代码。

我们仅仅需要做以下几步来完成转换: 我们将:

  1. 转换URLconf。
  2. 删除一些旧的、不再需要的代码。
  3. 引进基于Django通用视图的新视图。

为什么要重构代码?

一般来说,当编写一个Django应用时,你应该先评估一下通用视图是否可以解决你的问题,你应该在一开始使用它,而不是进行到一半时重构代码。 本教程目前为止是有意将重点放在以“困难的方式”编写视图,这是为将重点放在核心概念上。

就像在使用计算器之前你需要知道基本的数学一样。

修改URLconf

首先,打开polls/urls.py

polls/urls.py

from django.conf.urls import url
from . import views app_name='polls'
urlpatterns=[
url(r'^$',views.IndexView.as_view(),name='index'),
url(r'^(?P<pk>[0-9]+)/$',views.DetailView.as_view(),name='detail'),
url(r'^(?P<pk>[0-9]+)/results/$',views.ResultsView.as_view(),name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$',views.vote,name='vote')

注意在第二个和第三个模式的正则表达式中,匹配的模式的名字由<question_id>变成<pk>

修改视图

下一步,我们将删除旧的results、index和detail视图,并用django的通用视图代替,打开polls/views.py文件,并将它修改成:

polls/views.py

from django.shortcuts import get_object_or_404,render
from django.http import HttpResponseRedirct
from django.urls import reverse
from django.views import generic from .models import Choice,Question class IndexView(generic.ListView):
template_name='polls/index.html'
context_object_name='latest_question_list' def get_queryset(self):
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’的主键值,因此我们把polls/urls.py中question_id改成了pk以使通用视图可以找到主键值

默认情况下,通用视图DetailView使用一个叫做<app_name>/<model name>_detail.html的模板。在我们的例子中,它将使用‘polls/question_detail.html’模板。template_name属性是用来告诉django使用一个指定的模板名字,而不是自动生成的默认名字。我们也为template_name列表视图制定了results——这确保results视图和detail视图在渲染时具有不同的外观,即使他们在后台都是同一个DetailView。

类似的,ListView通用视图使用名为<app name>/<model name>_list.html的;我们使用template_name来告诉ListView使用我们现有的‘polls/index.html’模板

在之前的教程中,提供模板文件时都带有一个包含question和latest_question_list变量的context。对于Question,DetailView变量会自动提供——因为我们使用django的模型(question),django能够为context变量决定一个合适的名字。然而对于ListView,自动生成的context变量是question_list。为了覆盖这个行为,我们提供context_object_name属性,表示我们想用latest_question_list。作为一种替换方案,你可以改变你的模板来匹配新的context变量,但直接告诉django使用你想要的变量会省事很多

启动服务器,使用一下基于通用视图的新投票应用

编写你的第一个django应用程序4的更多相关文章

  1. 编写你的第一个django应用程序2

    从1停止的地方开始,我们将设置数据库,创建您的第一个模型,并快速介绍django自动生成的管理站点 数据库设置 现在,打开mysite/settings.py.这是一个普通的python模块,其中模块 ...

  2. 编写你的第一个django应用程序3

    这一篇从教程第2部分结尾的地方继续讲起.我们将继续编写投票应用,并且专注于如何创建公用界面--也被称为视图 概况 django视图概念是一类具有相同功能和末班的网页的集合,比如,在一个博客应用中,你可 ...

  3. Django教程:第一个Django应用程序(3)

    Django教程:第一个Django应用程序(3) 2013-10-08 磁针石 #承接软件自动化实施与培训等gtalk:ouyangchongwu#gmail.comqq 37391319 #博客: ...

  4. Django 2.0.1 官方文档翻译: 编写你的第一个 Django app,第一部分(Page 6)

    编写你的第一个 Django app,第一部分(Page 6)转载请注明链接地址 Django 2.0.1 官方文档翻译: Django 2.0.1.dev20171223092829 documen ...

  5. Django 2.0.1 官方文档翻译: 编写你的第一个 Django app,第五部分(Page 10)

    编写你的第一个 Django app,第五部分(Page 10)转载请注明链接地址 我们继续建设我们的 Web-poll 应用,本节我们会为它创建一些自动测试. 介绍自动测试 什么是自动测试 测试是简 ...

  6. 第一个Django应用程序_part3

    一.概述 此文延续第一个Django应用程序part2. 官方文档:https://docs.djangoproject.com/en/1.11/intro/tutorial03/ view是Djan ...

  7. 编写你的第一个Django应用

    安装 Python 作为一个 Python Web 框架,Django 需要 Python.更多细节请参见 我应该使用哪个版本的 Python 来配合 Django?. Python 包含了一个名为  ...

  8. 搭建你的第一个Django应用程序

    首先你要确保你机器上面安装了python:Python开发_python的安装 python的相关学习资料:http://www.cnblogs.com/hongten/tag/python/ 其次, ...

  9. Django教程:第一个Django应用程序(4)

    Django教程:第一个Django应用程序(4) 2013-10-09 磁针石 #承接软件自动化实施与培训等gtalk:ouyangchongwu#gmail.comqq 37391319 #博客: ...

随机推荐

  1. 最新版的 react-native 降级处理

    1.react-native 常见操作 (1)react-native init Demo (2)adb devices (3)react-native run-android (4)ipconfig ...

  2. Odoo12 重大改变

    Table of Contents 重构的功能 ORM 数据导入 库存 库存规则 MRP 多步路线 新功能 IoT     Odoo12 预计 2018/10 在 Odoo experience 20 ...

  3. TCP/IP详解 卷一(第二十章 TCP的成块数据流)

    本章将介绍TCP所使用的被称为滑动窗口协议的一种流量控制方法. 该协议允许发送方在停止并等待确认前可以连续发送多个分组,这样就可以加速数据的传输. 滑动窗口 下图用可视化的方法显示了滑动窗口协议 我们 ...

  4. UIWebView 加载网页、文件、 html

    UIWebView  是用来加载加载网页数据的一个框.UIWebView可以用来加载pdf word doc 等等文件 生成webview 有两种方法,1.通过storyboard 拖拽 2.通过al ...

  5. Eclipse更改默认工作环境编码为UTF-8(9.6)

    1 window---->preference------>General----->Workspace---->Text file encoding---->Other ...

  6. DelphiXe 中静态数组TByteArray和动态数组TBytes /array of byte 的区别

    在应用中发现静态数组和动态数组是有区别的: procedure TForm1.Button1Click(Sender: TObject);var  RsltStream: TMemoryStream; ...

  7. 网络相关系列之四:数据解析之SAX方式解析XML数据

    一.XML和Json数据的引入: 通常情况下.每一个须要訪问网络的应用程序都会有一个自己的server.我们能够向server提交数据,也能够从server获取数据.只是这个时候就有一个问题,这些数据 ...

  8. Sphinx之配置文件

    # # Sphinx configuration file sample # # WARNING! While this sample file mentions all available opti ...

  9. The best way to predict the future is to invent it,预测未来最好的方法是创造它!

    The best way to predict the future is to invent it,预测未来最好的方法是创造它! ——Smalltalk发明人Alan Kay “预测未来的最好方法, ...

  10. python中常用的base64 md5 aes des crc32等的加密解密

    1.base64 Python内置的base64模块可以实现base64.base32.base16.base85.urlsafe_base64的编码解码,python 3.x通常输入输出都是二进制形 ...