该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程

所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址。


一、表单form

为了接收用户的投票选择,我们需要在前端页面显示一个投票界面。让我们重写先前的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>

简要说明:

  • 上面的模板显示一系列单选按钮,按钮的值是选项的ID,按钮的名字是字符串"choice"。这意味着,当你选择了其中某个按钮,并提交表单,一个包含数据choice=#的POST请求将被发送到指定的url,#是被选择的选项的ID。这就是HTML表单的基本概念。
  • 如果你有一定的前端开发基础,那么form标签的action属性和method属性你应该很清楚它们的含义,action表示你要发送的目的url,method表示提交数据的方式,一般分POST和GET。
  • forloop.counter是DJango模板系统专门提供的一个变量,用来表示你当前循环的次数,一般用来给循环项目添加有序数标。
  • 由于我们发送了一个POST请求,就必须考虑一个跨站请求伪造的安全问题,简称CSRF(具体含义请百度)。Django为你提供了一个简单的方法来避免这个困扰,那就是在form表单内添加一条{% csrf_token %}标签,标签名不可更改,固定格式,位置任意,只要是在form表单内。这个方法对form表单的提交方式方便好使,但如果是用ajax的方式提交数据,那么就不能用这个方法了。

现在,让我们创建一个处理提交过来的数据的视图。前面我们已经写了一个“占坑”的vote视图的url(polls/urls.py):

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

以及“占坑”的vote视图函数(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):
# 发生choice未找到异常时,重新返回表单页面,并给出提示信息
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’]返回被选择选项的ID,并且值的类型永远是string字符串,那怕它看起来像数字!同样的,你也可以用类似的手段获取GET请求发送过来的数据,一个道理。
  • request.POST[’choice’]有可能触发一个KeyError异常,如果你的POST数据里没有提供choice键值,在这种情况下,上面的代码会返回表单页面并给出错误提示。PS:通常我们会给个默认值,防止这种异常的产生,例如request.POST[’choice’,None],一个None解决所有问题。
  • 在选择计数器加一后,返回的是一个HttpResponseRedirect而不是先前我们常用的HttpResponse。HttpResponseRedirect需要一个参数:重定向的URL。这里有一个建议,当你成功处理POST数据后,应当保持一个良好的习惯,始终返回一个HttpResponseRedirect。这不仅仅是对Django而言,它是一个良好的WEB开发习惯。
  • 我们在上面HttpResponseRedirect的构造器中使用了一个reverse()函数。它能帮助我们避免在视图函数中硬编码URL。它首先需要一个我们在URLconf中指定的name,然后是传递的数据。例如'/polls/3/results/',其中的3是某个question.id的值。重定向后将进入polls:results对应的视图,并将question.id传递给它。白话来讲,就是把活扔给另外一个路由对应的视图去干。

当有人对某个问题投票后,vote()视图重定向到了问卷的结果显示页面。下面我们来写这个处理结果页面的视图(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})

同样,还需要写个模板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/了,投票吧。你会看到一个结果页面,每投一次,它的内容就更新一次。如果你提交的时候没有选择项目,则会得到一个错误提示。

如果你在前面漏掉了一部分操作没做,比如没有创建choice选项对象,那么可以按下面的操作,补充一下:

F:\Django_course\mysite>python manage.py shell
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from polls.models import Question
>>> q = Question.objects.get(pk=1)
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Choice object>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: Choice object>
>>> q.choice_set.create(choice_text='Just hacking again', votes=0)
<Choice: Choice object>

为了方便大家,我将当前状态下的各主要文件内容一并贴出,供大家对照参考!

1--完整的mysite/urls.py文件如下:

from django.conf.urls import url,include
from django.contrib import admin urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^polls/', include('polls.urls')),
]

2--完整的mysite/settings.py文件如下:

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '85vvuta(p05ow!4pz2b0qbduu0%pq6x5q66-ei*pg+-lbdr#m^' # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [
'polls',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
] MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ROOT_URLCONF = 'mysite.urls' TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
] WSGI_APPLICATION = 'mysite.wsgi.application' # Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
} # Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
] # Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = True

3--完整的polls/views.py应该如下所示:

from django.shortcuts import reverse
from django.shortcuts import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.shortcuts import HttpResponse
from django.shortcuts import render
from .models import Choice
from .models import Question
from django.template import loader
# Create your views here. def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_question_list': latest_question_list,
}
return HttpResponse(template.render(context, request)) def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question}) def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': 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,)))

4--完整的polls/urls.py应该如下所示:

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

5--完整的polls/model.py文件如下:

from django.db import models
import datetime
from django.utils import timezone
# Create your models here. class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published') def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1) def __str__(self):
return self.question_text class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0) def __str__(self):
return self.choice_text

6--完整的polls/admin.py文件如下:

from django.contrib import admin

# Register your models here.

from .models import Question

admin.site.register(Question)

7--完整的templates/polls/index.html文件如下:

{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}

8--完整的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>

9--完整的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>

vote()视图没有对应的html模板,它直接跳转到results视图去了。

运行服务器,测试各功能:

这是问卷列表页面:

这是“what's up”问卷选项页面:

这是选择结果页面:

这是没有选择选项时,提示错误信息的页面:

请大家对比参考上面的内容,看看你自己的结果是否一样。

二、 使用类视图:减少重复代码

上面的detail、index和results视图的代码非常相似,有点冗余,这是一个程序猿不能忍受的。他们都具有类似的业务逻辑,实现类似的功能:通过从URL传递过来的参数去数据库查询数据,加载一个模板,利用刚才的数据渲染模板,返回这个模板。由于这个过程是如此的常见,Django很善解人意的帮你想办法偷懒,于是它提供了一种快捷方式,名为“类视图”。

现在,让我们来试试看将原来的代码改为使用类视图的方式,整个过程分三步走:

  • 修改URLconf设置
  • 删除一些旧的无用的视图
  • 采用基于类视图的新视图

PS:为什么本教程的代码来回改动这么频繁?

答:通常在写一个Django的app时,我们一开始就要决定使用类视图还是不用,而不是等到代码写到一半了才重构你的代码成类视图。但是本教程为了让你清晰的理解视图的内涵,“故意”走了一条比较曲折的路,因为我们的哲学是在你使用计算器之前你得先知道基本的数学公式

1.改良URLconf

打开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'),
]

请注意:在上面的的第2,3条目中将原来的<question_id>修改成了<pk>.

2.修改视图

接下来,打开polls/views.py文件,删掉index、detail和results视图,替换成Django的类视图,如下所示:

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
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):
"""返回最近发布的5个问卷."""
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):
... # 这个视图未改变!!!

在这里,我们使用了两种类视图ListViewDetailView(它们是作为父类被继承的)。这两者分别代表“显示一个对象的列表”和“显示特定类型对象的详细页面”的抽象概念。

  • 每一种类视图都需要知道它要作用在哪个模型上,这通过model属性提供。

  • DetailView类视图需要从url捕获到的称为"pk"的主键值,因此我们在url文件中将2和3条目的<question_id>修改成了<pk>

默认情况下,DetailView类视图使用一个称作<app name>/<model name>_detail.html的模板。在本例中,实际使用的是polls/detail.htmltemplate_name属性就是用来指定这个模板名的,用于代替自动生成的默认模板名。(一定要仔细观察上面的代码,对号入座,注意细节。)同样的,在resutls列表视图中,指定template_name'polls/results.html',这样就确保了虽然resulst视图和detail视图同样继承了DetailView类,使用了同样的model:Qeustion,但它们依然会显示不同的页面。(模板不同嘛!so easy!)

类似的,ListView类视图使用一个默认模板称为<app name>/<model name>_list.html。我们也使用template_name这个变量来告诉ListView使用我们已经存在的

"polls/index.html"模板,而不是使用它自己默认的那个。

在教程的前面部分,我们给模板提供了一个包含questionlatest_question_list的上下文变量。而对于DetailView,question变量会被自动提供,因为我们使用了Django的模型(Question),Django会智能的选择合适的上下文变量。然而,对于ListView,自动生成的上下文变量是question_list。为了覆盖它,我们提供了context_object_name属性,指定说我们希望使用latest_question_list而不是question_list

现在可以运行开发服务器,然后试试基于类视图的应用程序了。类视图是Django比较高级的一种用法,初学可能不太好理解,没关系,我们先有个印象。

Part 4:表单和类视图--Django从入门到精通系列教程的更多相关文章

  1. Part 3:视图和模板--Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  2. 多对多中间表详解 -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  3. Django简介--Django从入门到精通系列教程

    该系列教程系个人原创,并同步发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  4. Part 2:模型与后台管理admin站点--Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  5. Part 7:自定义admin站点--Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  6. 模型和字段 -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  7. 字段的参数 -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  8. 查询操作 -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

  9. Django中不返回QuerySets的API -- Django从入门到精通系列教程

    该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. Python及Django学习QQ群:453 ...

随机推荐

  1. ABP Zero示例项目问题总结

    1.ABP Zero项目,登录时出现如图“Empty or invalid anti forgery header token.”错误提示 ABP Zero项目,登录时出现如图“Empty or in ...

  2. GNS3的配置

    为了更好的了解协议我决定学习CCNA 安装好GNS3后我们打开 点击设置 先把iso解压,解压完成后倒入 保存结束 然后在输入idlepc get 设备名称 来计算idepc的值 idepc能让我们p ...

  3. linux(八)linux系统中查找文件二

    前面介绍的是find命令,我们发现一个find命令居然有那么多的命令,我看到都要晕了,不管没有关系,加油.相信自己! 一.grep命令 1.1.作用 Linux系统中grep命令是一种强大的文本搜索工 ...

  4. qscoj 128 喵哈哈村的魔法源泉(2)(模仿快速幂,好题)

    喵哈哈村的魔法源泉(2) 发布时间: 2017年5月9日 20:59   最后更新: 2017年5月9日 21:00   时间限制: 1000ms   内存限制: 128M 描述 喵哈哈村有一个魔法源 ...

  5. Vijos P1127 级数求和【模拟】

    级数求和 描述 已知:Sn= 1+1/2+1/3+…+1/n.显然对于任意一个整数K,当n足够大的时候,Sn大于K. 现给出一个整数K(1<=k<=15),要求计算出一个最小的n:使得Sn ...

  6. zookeeper的安装以及启动jps进程

    2.7.1安装 将下载好的安装包,解压到指定位置,这里为直接解压到当前位置,命令如下: tar -zxvf zk-{version}.tar.gz 修改zk配置,将zk安装目录下conf/zoo_sa ...

  7. 审计日志中的AOP

    审计跟踪(也称为审核日志)是一个安全相关的时间顺序记录,记录这些记录的目的是为已经影响在任何时候的详细操作,提供程序运行的证明文件记录.源或事件 MVC 自定义一个过滤器 public class A ...

  8. 织梦DedeCMS v5.7 实现导航条下拉菜单

    首先将下面这段代码贴到templets\default\footer.htm文件里(只要在此文件里就行,位置无所谓) <</span>script type='text/javasc ...

  9. WatchKit编程指南:Watch Apps--文本、标签以及图片

    文本和分类标签 为了在Watch app中展示文本,使用标签对象.分类标签支持格式化的文本,可以在运行时被程序修改. 要添加标签到界面控制器,可以把它拖到对应的故事版场景(storyboard),在这 ...

  10. 关于VC++中virtual ~的含义

    我知道virtual 的虚函数定义,~CMainFrame( )是析构函数,用来释放内存.C++的继承和派生内容.所有可以被用作基类的类一般都用虚析构函数当基类对象的指针或引用调用派生类对象时,如果基 ...