一、表单form

为了接收用户的投票选择,我们需要在前端页面显示一个投票界面。让我们重写先前的polls/detail.html文件,代码如下:

  1. <h1>{{ question.question_text }}</h1>
  2. {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
  3. <form action="{% url 'polls:vote' question.id %}" method="post">
  4. {% csrf_token %}
  5. {% for choice in question.choice_set.all %}
  6. <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
  7. <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
  8. {% endfor %}
  9. <input type="submit" value="Vote" />

简要说明:

  • 上面的模板显示一系列单选按钮,按钮的值是选项的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):

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

以及“占坑”的vote视图函数(polls/views.py),我们把坑填起来:

  1. from django.shortcuts import get_object_or_404, render
  2. from django.http import HttpResponseRedirect, HttpResponse
  3. from django.urls import reverse
  4. from .models import Choice, Question
  5. # ...
  6. def vote(request, question_id):
  7. question = get_object_or_404(Question, pk=question_id)
  8. try:
  9. selected_choice = question.choice_set.get(pk=request.POST['choice'])
  10. except (KeyError, Choice.DoesNotExist):
  11. # 发生choice未找到异常时,重新返回表单页面,并给出提示信息
  12. return render(request, 'polls/detail.html', {
  13. 'question': question,
  14. 'error_message': "You didn't select a choice.",
  15. })
  16. else:
  17. selected_choice.votes += 1
  18. selected_choice.save()
  19. # 成功处理数据后,自动跳转到结果页面,防止用户连续多次提交。
  20. 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):

  1. from django.shortcuts import get_object_or_404, render
  2. def results(request, question_id):
  3. question = get_object_or_404(Question, pk=question_id)
  4. * return render(request, 'polls/results.html', {'question': question})

同样,还需要写个模板polls/templates/polls/results.html。(路由、视图、模板、模型!都是这个套路....)

  1. <h1>{{ question.question_text }}</h1>
  2. <ul>
  3. {% for choice in question.choice_set.all %}
  4. <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
  5. {% endfor %}
  6. </ul>
  7. <a href="{% url 'polls:detail' question.id %}">Vote again?</a>

现在你可以到浏览器中访问/polls/1/了,投票吧。你会看到一个结果页面,每投一次,它的内容就更新一次。如果你提交的时候没有选择项目,则会得到一个错误提示。

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

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

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

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

  1. from django.conf.urls import url,include
  2. from django.contrib import admin
  3. urlpatterns = [
  4. url(r'^admin/', admin.site.urls),
  5. url(r'^polls/', include('polls.urls')),
  6. ]

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

  1. import os
  2. # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
  3. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  4. # Quick-start development settings - unsuitable for production
  5. # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
  6. # SECURITY WARNING: keep the secret key used in production secret!
  7. SECRET_KEY = '85vvuta(p05ow!4pz2b0qbduu0%pq6x5q66-ei*pg+-lbdr#m^'
  8. # SECURITY WARNING: don't run with debug turned on in production!
  9. DEBUG = True
  10. ALLOWED_HOSTS = []
  11. # Application definition
  12. INSTALLED_APPS = [
  13. 'polls',
  14. 'django.contrib.admin',
  15. 'django.contrib.auth',
  16. 'django.contrib.contenttypes',
  17. 'django.contrib.sessions',
  18. 'django.contrib.messages',
  19. 'django.contrib.staticfiles',
  20. ]
  21. MIDDLEWARE = [
  22. 'django.middleware.security.SecurityMiddleware',
  23. 'django.contrib.sessions.middleware.SessionMiddleware',
  24. 'django.middleware.common.CommonMiddleware',
  25. 'django.middleware.csrf.CsrfViewMiddleware',
  26. 'django.contrib.auth.middleware.AuthenticationMiddleware',
  27. 'django.contrib.messages.middleware.MessageMiddleware',
  28. 'django.middleware.clickjacking.XFrameOptionsMiddleware',
  29. ]
  30. ROOT_URLCONF = 'mysite.urls'
  31. TEMPLATES = [
  32. {
  33. 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  34. 'DIRS': [os.path.join(BASE_DIR, 'templates')]
  35. ,
  36. 'APP_DIRS': True,
  37. 'OPTIONS': {
  38. 'context_processors': [
  39. 'django.template.context_processors.debug',
  40. 'django.template.context_processors.request',
  41. 'django.contrib.auth.context_processors.auth',
  42. 'django.contrib.messages.context_processors.messages',
  43. ],
  44. },
  45. },
  46. ]
  47. WSGI_APPLICATION = 'mysite.wsgi.application'
  48. # Database
  49. # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
  50. DATABASES = {
  51. 'default': {
  52. 'ENGINE': 'django.db.backends.sqlite3',
  53. 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
  54. }
  55. }
  56. # Password validation
  57. # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
  58. AUTH_PASSWORD_VALIDATORS = [
  59. {
  60. 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
  61. },
  62. {
  63. 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
  64. },
  65. {
  66. 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
  67. },
  68. {
  69. 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
  70. },
  71. ]
  72. # Internationalization
  73. # https://docs.djangoproject.com/en/1.11/topics/i18n/
  74. LANGUAGE_CODE = 'en-us'
  75. TIME_ZONE = 'Asia/Shanghai'
  76. USE_I18N = True
  77. USE_L10N = True
  78. USE_TZ = True

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

  1. from django.shortcuts import reverse
  2. from django.shortcuts import HttpResponseRedirect
  3. from django.shortcuts import get_object_or_404
  4. from django.shortcuts import HttpResponse
  5. from django.shortcuts import render
  6. from .models import Choice
  7. from .models import Question
  8. from django.template import loader
  9. # Create your views here.
  10. def index(request):
  11. latest_question_list = Question.objects.order_by('-pub_date')[:5]
  12. template = loader.get_template('polls/index.html')
  13. context = {
  14. 'latest_question_list': latest_question_list,
  15. }
  16. return HttpResponse(template.render(context, request))
  17. def detail(request, question_id):
  18. question = get_object_or_404(Question, pk=question_id)
  19. return render(request, 'polls/detail.html', {'question': question})
  20. def results(request, question_id):
  21. question = get_object_or_404(Question, pk=question_id)
  22. return render(request, 'polls/results.html', {'question': question})
  23. def vote(request, question_id):
  24. question = get_object_or_404(Question, pk=question_id)
  25. try:
  26. selected_choice = question.choice_set.get(pk=request.POST['choice'])
  27. except (KeyError, Choice.DoesNotExist):
  28. return render(request, 'polls/detail.html', {
  29. 'question': question,
  30. 'error_message': "You didn't select a choice.",
  31. })
  32. else:
  33. selected_choice.votes += 1
  34. selected_choice.save()
  35. return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

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

  1. from django.conf.urls import url
  2. from . import views
  3. app_name = 'polls'
  4. urlpatterns = [
  5. # ex: /polls/
  6. url(r'^$', views.index, name='index'),
  7. # ex: /polls/5/
  8. url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
  9. # ex: /polls/5/results/
  10. url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
  11. # ex: /polls/5/vote/
  12. url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
  13. ]

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

  1. from django.db import models
  2. import datetime
  3. from django.utils import timezone
  4. # Create your models here.
  5. class Question(models.Model):
  6. question_text = models.CharField(max_length=200)
  7. pub_date = models.DateTimeField('date published')
  8. def was_published_recently(self):
  9. return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
  10. def __str__(self):
  11. return self.question_text
  12. class Choice(models.Model):
  13. question = models.ForeignKey(Question, on_delete=models.CASCADE)
  14. choice_text = models.CharField(max_length=200)
  15. votes = models.IntegerField(default=0)
  16. def __str__(self):
  17. return self.choice_text

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

  1. from django.contrib import admin
  2. # Register your models here.
  3. from .models import Question
  4. admin.site.register(Question)

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

  1. {% if latest_question_list %}
  2. <ul>
  3. {% for question in latest_question_list %}
  4. <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
  5. {% endfor %}
  6. </ul>
  7. {% else %}
  8. <p>No polls are available.</p>
  9. {% endif %}

8--完整的templates/polls/detail.html文件如下:

  1. <h1>{{ question.question_text }}</h1>
  2. {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
  3. <form action="{% url 'polls:vote' question.id %}" method="post">
  4. {% csrf_token %}
  5. {% for choice in question.choice_set.all %}
  6. <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
  7. <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
  8. {% endfor %}
  9. <input type="submit" value="Vote" />
  10. </form>

9--完整的templates/polls/results.html文件如下:

  1. <h1>{{ question.question_text }}</h1>
  2. <ul>
  3. {% for choice in question.choice_set.all %}
  4. <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
  5. {% endfor %}
  6. </ul>
  7. <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文件,将其修改成下面的样子:

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

ps: 将原来的<question_id>修改成了.

2.修改视图

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

  1. from django.shortcuts import get_object_or_404, render
  2. from django.http import HttpResponseRedirect
  3. from django.urls import reverse
  4. from django.views import generic
  5. from .models import Choice, Question
  6. class IndexView(generic.ListView):
  7. template_name = 'polls/index.html'
  8. context_object_name = 'latest_question_list'
  9. def get_queryset(self):
  10. """返回最近发布的5个问卷."""
  11. return Question.objects.order_by('-pub_date')[:5]
  12. class DetailView(generic.DetailView):
  13. model = Question
  14. template_name = 'polls/detail.html'
  15. class ResultsView(generic.DetailView):
  16. model = Question
  17. template_name ='polls/results.html'
  18. def vote(request, question_id):
  19. ... # 这个视图未改变!!!

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

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

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

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

类似的,ListView类视图使用一个默认模板称为/_list.html。我们也使用template_name这个变量来告诉ListView使用我们已经存在的 "polls/index.html"模板,而不是使用它自己默认的那个。

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

六、Django之表单和类视图-Part 4的更多相关文章

  1. Part 4:表单和类视图--Django从入门到精通系列教程

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

  2. 第一个Django应用 - 第四部分:表单和类视图

    一.表单form 为了接收用户的投票选择,我们需要在前端页面显示一个投票界面.让我们重写先前的polls/detail.html文件,代码如下: <h1>{{ question.quest ...

  3. Django内置的通用类视图

    1.ListView 表示对象列表的一个页面. 执行这个视图的时候,self.object_list将包含视图正在操作的对象列表(通常是一个查询集,但不是必须). 属性: model: 指定模型 te ...

  4. Django学习笔记之Django Form表单详解

    知识预览 构建一个表单 在Django 中构建一个表单 Django Form 类详解 使用表单模板 回到顶部 构建一个表单 假设你想在你的网站上创建一个简单的表单,以获得用户的名字.你需要类似这样的 ...

  5. django Form表单的使用

    Form django表单系统中,所有的表单类都作为django.forms.Form的子类创建,包括ModelForm 关于django的表单系统,主要分两种 基于django.forms.Form ...

  6. Django form表单 组件

    目录 Django form表单 组件 Form 组件介绍 普通方式手写注册功能 使用form组件实现注册功能 Form 常用字段与插件 常用字段(必备) 字段参数(必备) 内置验证(必备) 自定义效 ...

  7. python运维开发(十九)----Django后台表单验证、session、cookie、model操作

    内容目录: Django后台表单验证 CSRF加密传输 session.cookie model数据库操作 Django后台Form表单验证 Django中Form一般有2种功能: 1.用于做用户提交 ...

  8. django form表单验证

    一. django form表单验证引入 有时时候我们需要使用get,post,put等方式在前台HTML页面提交一些数据到后台处理例 ; <!DOCTYPE html> <html ...

  9. JS表单验证类HTML代码实例

    以前用的比较多的一个JS表单验证类,对于个人来说已经够用了,有兴趣的可以在此基础上扩展成ajax版本.本表单验证类囊括了密码验证.英文4~10个 字符验证. 中文非空验证.大于10小于100的数字.浮 ...

随机推荐

  1. 关于Calculator的第四次作业

    一.魔法传送门: 问题描述:点我点我点我! 仓库地址:点我点我点我! 二.网上资料: sstream的介绍及应用 后缀表达式C++代码 中缀转前缀及后缀方法 C++计算器源代码 三.实现过程: 在看到 ...

  2. 手写阻塞队列(Condition实现)

    自己实现阻塞队列的话可以采用Object下的wait和notify方法,也可以使用Lock锁提供的Condition来实现,本文就是自己手撸的一个简单的阻塞队列,部分借鉴了JDK的源码.Ps:最近看面 ...

  3. php 上传大文件主要涉及配置upload_max_filesize和post_max_size两个选项。

    今天在做上传的时候出现一个非常怪的问题,有时候表单提交可以获取到值,有时候就获取不到了,连普通的字段都获取不到了,苦思冥想还没解决,群里人问我upload_max_filesize的值改了吗,我说改了 ...

  4. 026.3 网络编程 TCP聊天

    分为客户端和服务端,分别进行收发操作##########################################################################客户端:###思 ...

  5. Perl之my与local

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/sunshoupo211/article/details/31745909    在函数定义中,使用m ...

  6. [BJWC2008]雷涛的小猫

    嘟嘟嘟 dp. 刚开始我想的是dp[i][j]表示在第 i 棵树上,高度为h能吃到的最多的果子,如此能得到转移方程: dp[i][j] = max(dp[i][j + 1], dp[k][j + de ...

  7. openstack镜像制作思路、指导及问题总结

    一.思路就4步:1.创建镜像文件2.用nova-compute自带的kvm,启动.iso文件,用vncviewer完成OS的安装过程3.OS安装完毕,停止虚拟机,kvm重启镜像,安装必要的软件4.后续 ...

  8. React 入门学习笔记1

    摘自阮一峰:React入门实例教程,转载请注明出处. 一. 使用React的html模板 使用React, 我们需要加载3个库,react.js, react-dom.js, 和browser.js. ...

  9. 【转】Spring Boot干货系列:(六)静态资源和拦截器处理

    前言 本章我们来介绍下SpringBoot对静态资源的支持以及很重要的一个类WebMvcConfigurerAdapter. 正文 前面章节我们也有简单介绍过SpringBoot中对静态资源的默认支持 ...

  10. [转]地图投影的N种姿势

    此处直接给出原文链接: 1.地图投影的N种姿势 2.GIS理论(墨卡托投影.地理坐标系.地面分辨率.地图比例尺.Bing Maps Tile System)