Python的Flask框架中web表单的教程


Posted in Python onApril 20, 2015

 概要

在前面章节我们为主页定义了一个简单的模板,部分尚未实现的模块如用户或帖子等使用模拟的对象作为临时占位。

本章我们将看到如何利用web表单填补这些空白。

web表单是web应用中最基本的构建要素,我们将通过表单来实现用户发帖和应用登录功能。

完成本章内容你需要基于前面章节完成的微博应用代码,请确认这些代码已安装并能正常运行。

配置

Flask-WTF是WTForms项目的Flask框架扩展,我们将用他来帮助我们处理web表单。

大部分Flask扩展都需要定义相关配置项,所以我们先来在应用根目录下创建一个配置文件以备使用。我们先这样创建 (fileconfig.py):
 

CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'

很简单吧,这是Flask-WTF需要用到的2个配置项。CSRF_ENABLED配置启用了跨站请求攻击保护,大部分情况下你都需要开启此功能,这能使你的应用更安全。

SECRET_KEY设置当CSRF启用时有效,这将生成一个加密的token供表单验证使用,你要确保这个KEY足够复杂不会被简单推测。

现在这个配置文件已经基本可用了。项目创建完成我们可以创建如下文件并编辑(fileapp/__init__.py):
 

from flask import Flask
 
app = Flask(__name__)
app.config.from_object('config')
 
from app import views

用户登录表单

使用Flask-WTF创建的表单就像一个对象,需要从Form类继承子类。然后在这个子类中定义一些类的属性变量作为表单字段就可以了。

我们要创建一个登录表单,用来进行用户身份识别。但跟平常需要验证用户名和密码的登录方式不同,我们将使用 OpenId 来处理登录过程。使用OpenId的好处就是我们不用管那些用户名和密码的认证过程,交给 OpenId 去搞定,它会返回给我们用户验证后的数据。这样对于使用我们网站的用户而言也更安全。

使用 OpenId 登录只需要一个字符串,然后发送给 OpenId 服务器就行了。另外我们还需要在表单中加一个“记住我” 的选项框,这个是送给那些不想每次来我们网站都要进行身份认证的人。选择这个选项后,首次登录时会用cookie在他们的浏览器上记住他们的登录信息,下次再进入网站时就不需要进行登录操作。

开始我们的第一个表单吧 (fileapp/forms.py):
 

from flask.ext.wtf import Form, TextField, BooleanField
from flask.ext.wtf import Required
 
class LoginForm(Form):
  openid = TextField('openid', validators = [Required()])
  remember_me = BooleanField('remember_me', default = False)

欣赏一下这个类,多么的简洁,多么的一目了然。如此简单,但又十分的富有内涵。我们引入了一个 Form 类,然后继承这个类,按需求还添加了 TextField 和 BooleanField 这两个字段。

另外还引入了一个表单验证函数 Required,这种验证函数可以附加在字段里面,在用户提交表单时它们会用来检查用户填写的数据。这个 Required 函数是用来防止用户提交空数据。Flask-WTF 中还有很多不同作用的表单验证函数,我们将会在后面使用到它们。


表单模板

现在我们的问题就是需要一个显示这个登录表单的模板。好消息是我们刚刚创建的登录表单类知道如何把字段转换成HTML,所以我们只需要把注意力集中到页面布局上。下面就是我们的登录表单的模板 (fileapp/templates/login.html):
 

<!-- extend from base layout -->
{% extends "base.html" %}
 
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" name="login">
  {{form.hidden_tag()}}
  <p>
    Please enter your OpenID:<br>
    {{form.openid(size=80)}}<br>
  </p>
  <p>{{form.remember_me}} Remember Me</p>
  <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

容我??乱幌拢?谡飧瞿0逯校?颐怯忠淮问褂昧四0寮坛械姆绞健J褂 extends 语句从 base.html 继承模板内容。我们会在后面创建的模板中继续使用这种方式,这样可以使我们所有的页面布局保持一致。

这个登录模板跟普通的HTML表单有些明显的区别,它使用模板参数 {{ ... }} 来实例化表单字段,而表单字段又来源于我们刚刚定义的表单类,模板参数中使用了 form 这个名称。当我们使用视图函数引用表单类并渲染到模板时,我们要特别注意这个把表单类传递到模板的变量名。

我们在配置中开启了CSRF(跨站伪造请求)功能,模板参数 {{ form.hidden_tag() }} 会被替换成一个具有防止CSRF功能的隐藏表单字段。在开启了CSRF功能后,所有模板的表单中都需要添加这个模板参数。

我们定义的表单对象中的字段同样也能被模板渲染,只需要在模板合适的位置添加类似于 {{ form.field_name }} 这样的模板参数,相关字段就会在被定义的位置出现。另外还有一些字段是可以传参数,比如这个 openid 字段,我们就添加了一个参数让它显示的宽度增加到80个字符。

由于我们没有在表单中定义一个提交功能的按钮,所以在这里只能以普通表单字段的方式来做了。不过说起来区区一个按钮,在表单中跟任何数据都没有关系,的确也没有在表单类中定义的必要。

表单视图

见证奇迹的时刻最后一步,我们马上要来写一个渲染登录表单对象到模板的视图函数。

这个函数相当的简单无趣,因为我们只需要把表单对象传递给模板就行了。下面就是我们这个视图函数的全部内容 (fileapp/views.py):
 

from flask import render_template, flash, redirect
from app import app
from forms import LoginForm
 
# index view function suppressed for brevity
 
@app.route('/login', methods = ['GET', 'POST'])
def login():
  form = LoginForm()
  return render_template('login.html',
    title = 'Sign In',
    form = form)

我们引入登录表单类,然后把它实例化到一个变量,最后再把这个变量传给模板。要渲染表单字段必须的事情也就这些。

上面的代码中还引入了两个新对象: falsh 和 redirect, 这个先甭理它们,稍后才用得上。

另外还做了一件事就是在路由装饰器中添加一个新方法。让 Flask 明白我们这个视图函数支持 GET 和 POST 请求。否则这个视图函数只会响应 GET 请求。我们需要得到用户填写表单后提交的数据,这些数据是从 POST 请求中传递过来的。

你可以通过在浏览器中测试这个程序来了解上面所说的。 按照视图函数关联的路由,你应该在浏览器中输入 http://localhost:5000/login

由于我们还没有写任何接收数据的代码,所以现在你在页面中点提交按钮还没有任何效果。

从表单中接收数据

另外值得一提的是, Flask-WTF 对表单提交数据的处理使我们的接下来要做的事情变得简单了。下面就是我们这个登录视图函数的新版本, 加入了表单数据验证和处理 (fileapp/views.py):
 

@app.route('/login', methods = ['GET', 'POST'])
def login():
  form = LoginForm()
  if form.validate_on_submit():
    flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data))
    return redirect('/index')
  return render_template('login.html',
    title = 'Sign In',
    form = form)

validate_on_submit() 这个方法做了表单处理的所有工作。如果你在表单向用户提供数据时(举个栗子:用户在它之前修改了一下提交的数据) 时调用此方法,它会返回 False。发生这样的情况时,你懂的。不懂?就是提交的数据验证不通过,你要继续渲染模板。

在提交请求时调用了表单的 validate_on_submit() 方法后,它会从请求中获取所有提交的数据,然后使用表单字段中绑定的验证函数进行数据验证。在所有的数据都验证通过时会返回 True. 这就意味着你可以放心的使用这些表单数据了。

只要有一个字段验证不通过,它都会返回 False. 这时就需要我们返回数据给用户,让他们来纠正一下错误数据。接下来我们将会看到在数据验证失败时,如何把错误消息显示给用户。

当 validate_on_submit() 方法返回 True 的时候,我们的视图函数又会调用两个新的函数。它们都是从Flask 中引入的,flash 函数用来在下一个打开的页面中显示定义的消息。我们现在用它用来做调试。因为我们现在还没有做用户登录模块, 所以只需要把用户提交上来的数据显示一下就行了。flash 函数非常有用,比如为用户的一些操作提供消息反馈。

flash 函数提供的消息不会自动出现在我们的网站页面中,所以我们需要做点事情让它在页面中显示出来。为了让我们所有页面都能有这项激动人心的功能,所以就把它添加到基础模板中吧, 下面是更新后的基础模板 (fileapp/templates/base.html):
 

<html>
 <head>
  {% if title %}
  <title>{{title}} - microblog</title>
  {% else %}
  <title>microblog</title>
  {% endif %}
 </head>
 <body>
  <div>Microblog: <a href="/index">Home</a></div>
  <hr>
  {% with messages = get_flashed_messages() %}
  {% if messages %}
  <ul>
  {% for message in messages %}
    <li>{{ message }} </li>
  {% endfor %}
  </ul>
  {% endif %}
  {% endwith %}
  {% block content %}{% endblock %}
 </body>
</html>

模板中显示 flash 消息的功能希望你能明白。

在视图函数中我们使用的另一个新函数就是 redirect. 这个函数会通知用户的浏览器跳转到指定的地址。在我们的视图函数中,我们使用它跳转到了首页。注意跳转结束后页面上还会显示 flash 函数传递的消息哦。

激动人心的时刻到了,运行我们的程序吧,看看表单是如何工作的吧。不要填写表单中的 openid 字段,看看 Required 这个验证函数是如何发挥威力,把一切发起空数据的请求阻止在千里之外。

改善一下字段验证

我们程序目前状况不错,提交不合要求的数据会被阻止,还会返回表单让用户修改,基本满足我们要求。

但似乎还少点什么。如果我们在用户提交数据失败后给用户点提示,让他们知道什么原因引起的,岂不妙哉!太幸运了,用 Flask-WTF 可以轻松解决这个问题。

当表单字段验证失败时, Flask-WTF 会添加一个错误消息到表单对象。这些消息在模板中也是可以使用的,所以我们只需要在模板中添加一点点东西就OK了。

这个就是我们添加了验证消息的登录模板 (fileapp/templates/login.html):
 

<!-- extend base layout -->
{% extends "base.html" %}
 
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" name="login">
  {{form.hidden_tag()}}
  <p>
    Please enter your OpenID:<br>
    {{form.openid(size=80)}}<br>
    {% for error in form.errors.openid %}
    <span style="color: red;">[{{error}}]</span>
    {% endfor %}<br>
  </p>
  <p>{{form.remember_me}} Remember Me</p>
  <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

我们仅在 openid 字段的右边添加了一个循环语句,它会把openid字段验证失败的消息都显示出来。不论你的表单有多少字段,所有表单字段验证失败的错误消息都可以用 form.errors.字段名 这种方式来使用。这个表单中我们的是 form.errors.openid。为了让错误消息引起用户的注意,我们还给消息添加了显示红色的 css 样式。
 
处理 OpenID 登录

现实生活中,我们发现有很多人都不知道他们拥有一些公共账号。一部分大牌的网站或服务商都会为他们的会员提供公共账号的认证。举个栗子,如果你有一个 google 账号,其实你就有了一个公共账号,类似的还有 Yahoo, AOL, Flickr 等。

为了方便我们的用户能简单的使用他们的公共账号,我们将把这些公共账号的链接添加到一个列表,这样用户就不用自手工输入了。

我们要把一些提供给用户的公共账号服务商定义到一个列表里面,这个列表就放到配置文件中吧 (fileconfig.py):
 

CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'
 
OPENID_PROVIDERS = [
  { 'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id' },
  { 'name': 'Yahoo', 'url': 'https://me.yahoo.com' },
  { 'name': 'AOL', 'url': 'http://openid.aol.com/<username>' },
  { 'name': 'Flickr', 'url': 'http://www.flickr.com/<username>' },
  { 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }]

接下来就是要在我们的登录视图函数中使用这个列表了:
 

@app.route('/login', methods = ['GET', 'POST'])
def login():
  form = LoginForm()
  if form.validate_on_submit():
    flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data))
    return redirect('/index')
  return render_template('login.html',
    title = 'Sign In',
    form = form,
    providers = app.config['OPENID_PROVIDERS'])

我们从 app.config 中引入了公共账号服务商的配置列表,然后把它作为一个参数通过 render_template 函数引入到模板。

接下来要做的我想你也猜得到,我们需要在登录模板中把这些服务商链接显示出来。
 

<!-- extend base layout -->
{% extends "base.html" %}
 
{% block content %}
<script type="text/javascript">
function set_openid(openid, pr)
{
  u = openid.search('<username>')
  if (u != -1) {
    // openid requires username
    user = prompt('Enter your ' + pr + ' username:')
    openid = openid.substr(0, u) + user
  }
  form = document.forms['login'];
  form.elements['openid'].value = openid
}
</script>
<h1>Sign In</h1>
<form action="" method="post" name="login">
  {{form.hidden_tag()}}
  <p>
    Please enter your OpenID, or select one of the providers below:<br>
    {{form.openid(size=80)}}
    {% for error in form.errors.openid %}
    <span style="color: red;">[{{error}}]</span>
    {% endfor %}<br>
    |{% for pr in providers %}
    <a href="javascript:set_openid('{{pr.url}}', '{{pr.name}}');">{{pr.name}}</a> |
    {% endfor %}
  </p>
  <p>{{form.remember_me}} Remember Me</p>
  <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

这次的模板添加的东西似乎有点多。一些公共账号需要提供用户名,为了解决这个我们用了点 javascript。当用户点击相关的公共账号链接时,需要用户名的公共账号会提示用户输入用户名, javascript 会把用户名处理成可用的公共账号,最后再插入到 openid 字段的文本框中。
 

下面这个是在登录页面点击 google 链接后显示的截图: 

Python的Flask框架中web表单的教程

 结束语

我们目前在用户登录表单中取得了一些进展,但用户登入这个网站还是啥事儿都不能干。目前我们还一直在登录的界面上小打小闹,是因为我们还没有记录一切的数据库。

所以从下节开始,我们将会整一个数据库起来,然后真正完成我们的登录系统。不要走开,请继续关注。

微博(microblog) 当前版本源码从下面地址下载:

下载地址:microblog-0.3.zip

Python 相关文章推荐
用Python实现换行符转换的脚本的教程
Apr 16 Python
Python实现随机生成有效手机号码及身份证功能示例
Jun 05 Python
Python随机生成均匀分布在三角形内或者任意多边形内的点
Dec 14 Python
对python中的高效迭代器函数详解
Oct 18 Python
python 不同方式读取文件速度不同的实例
Nov 09 Python
Django对数据库进行添加与更新的例子
Jul 12 Python
python爬虫增加访问量的方法
Aug 22 Python
python爬虫开发之urllib模块详细使用方法与实例全解
Mar 09 Python
python编写俄罗斯方块
Mar 13 Python
Python面向对象魔法方法和单例模块代码实例
Mar 25 Python
Python命令行参数定义及需要注意的地方
Nov 30 Python
Python OpenCV 图像平移的实现示例
Jun 04 Python
在Python的Flask框架中使用模版的入门教程
Apr 20 #Python
使用Node.js和Socket.IO扩展Django的实时处理功能
Apr 20 #Python
利用Python的Django框架中的ORM建立查询API
Apr 20 #Python
对于Python的框架中一些会话程序的管理
Apr 20 #Python
介绍Python的Django框架中的QuerySets
Apr 20 #Python
使用Python的Django框架实现事务交易管理的教程
Apr 20 #Python
简化Python的Django框架代码的一些示例
Apr 20 #Python
You might like
德生PL660的电路分析和打磨
2021/03/02 无线电
通用PHP动态生成静态HTML网页的代码
2010/03/04 PHP
rephactor 优秀的PHP的重构工具
2011/06/09 PHP
PHP连接MySQL查询结果中文显示乱码解决方法
2013/10/25 PHP
php中get_defined_constants函数用法实例分析
2015/05/12 PHP
PHP session垃圾回收机制实例分析
2019/06/28 PHP
繁简字转换功能
2006/07/19 Javascript
jQuery DIV弹出效果实现代码
2009/07/03 Javascript
js导入导出excel(实例代码)
2013/11/25 Javascript
Js可拖拽放大的层拖动特效实现方法
2015/02/25 Javascript
JavaScript数组和循环详解
2015/04/27 Javascript
Vue.js每天必学之数据双向绑定
2016/09/05 Javascript
Jquery Easyui进度条组件Progress使用详解(8)
2020/03/26 Javascript
微信小程序 图片绝对定位(背景图片)
2017/04/05 Javascript
大白话讲解JavaScript的Promise
2017/04/06 Javascript
Express+Nodejs 下的登录拦截实现代码
2017/07/01 NodeJs
详解nuxt sass全局变量(公共scss解决方案)
2018/06/27 Javascript
vue elementui form表单验证的实现
2018/11/11 Javascript
jquery层次选择器的介绍
2019/01/18 jQuery
layui实现下拉框三级联动
2019/07/26 Javascript
vue使用transition组件动画效果的实例代码
2021/01/28 Vue.js
python opencv设置摄像头分辨率以及各个参数的方法
2018/04/02 Python
pytorch使用Variable实现线性回归
2019/05/21 Python
Django 源码WSGI剖析过程详解
2019/08/05 Python
python实现不同数据库间数据同步功能
2021/02/25 Python
西班牙创意礼品和小工具网上商店:Curiosite
2016/07/26 全球购物
eBay德国站:eBay.de
2017/09/14 全球购物
SOKOLOV官网:俄罗斯珠宝首饰品牌
2021/01/02 全球购物
生物技术专业研究生自荐信
2013/09/22 职场文书
出纳岗位职责范本
2013/12/01 职场文书
班主任经验交流会主持词
2014/04/01 职场文书
投资合作协议书范本
2014/04/17 职场文书
煤矿安全知识竞赛活动总结
2014/07/07 职场文书
2015年六一儿童节活动方案
2015/05/05 职场文书
2015年新农村建设指导员工作总结
2015/07/24 职场文书
2019经典广告词集锦!
2019/07/02 职场文书