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实现排序算法
Feb 14 Python
浅析python 内置字符串处理函数的使用方法
Jun 11 Python
Python实现抓取网页生成Excel文件的方法示例
Aug 05 Python
Python图像处理之gif动态图的解析与合成操作详解
Dec 30 Python
python批量获取html内body内容的实例
Jan 02 Python
ipython和python区别详解
Jun 26 Python
在notepad++中实现直接运行python代码
Dec 18 Python
django 链接多个数据库 并使用原生sql实现
Mar 28 Python
浅谈python 中的 type(), dtype(), astype()的区别
Apr 09 Python
Python grequests模块使用场景及代码实例
Aug 10 Python
Python爬虫之Selenium警告框(弹窗)处理
Dec 04 Python
神经网络训练采用gpu设置的方式
Mar 03 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
php小型企业库存管理系统的设计与实现代码
2011/05/16 PHP
适用于抽奖程序、随机广告的PHP概率算法实例
2014/04/09 PHP
PHP实现删除字符串中任何字符的函数
2015/08/11 PHP
漂亮的thinkphp 跳转页封装示例
2019/10/16 PHP
一些有关检查数据的JS代码
2006/09/07 Javascript
用RadioButten或CheckBox实现div的显示与隐藏
2013/09/21 Javascript
jquery判断元素的子元素是否存在的示例代码
2014/02/04 Javascript
jQuery()方法的第二个参数详解
2015/04/29 Javascript
javascript+HTML5 Canvas绘制转盘抽奖
2020/05/16 Javascript
用JS实现图片轮播效果代码(一)
2016/06/26 Javascript
jquery 多个radio的click事件实例
2016/12/03 Javascript
canvas绘制多边形
2017/02/24 Javascript
node+vue实现用户注册和头像上传的实例代码
2017/07/20 Javascript
Bootstrap图片轮播效果详解
2017/10/17 Javascript
vue2.0模拟锚点的实例
2018/03/14 Javascript
web3.js增加eth.getRawTransactionByHash(txhash)方法步骤
2018/03/15 Javascript
使用vue-route 的 beforeEach 实现导航守卫(路由跳转前验证登录)功能
2018/03/22 Javascript
vue2.0 资源文件assets和static的区别详解
2018/04/08 Javascript
jQuery实现表单动态添加数据并提交的方法
2018/07/19 jQuery
微信小程序实现九宫格抽奖
2020/04/15 Javascript
微信小程序在其他页面监听globalData中值的变化
2019/07/15 Javascript
vue-resource 拦截器interceptors使用详解
2021/01/18 Vue.js
Python实现检测服务器是否可以ping通的2种方法
2015/01/01 Python
详解flask入门模板引擎
2018/07/18 Python
浅谈python3.x pool.map()方法的实质
2019/01/16 Python
python__name__原理及用法详解
2019/11/02 Python
解决Keras中循环使用K.ctc_decode内存不释放的问题
2020/06/29 Python
浅谈TensorFlow之稀疏张量表示
2020/06/30 Python
Python+Opencv实现把图片、视频互转的示例
2020/12/17 Python
小米旗下精品生活电商平台:小米有品
2018/12/18 全球购物
创先争优一句话承诺
2014/05/29 职场文书
三关爱志愿服务活动方案
2014/08/17 职场文书
大一新生期末自我评价
2014/09/12 职场文书
2014年保险业务员工作总结
2014/12/23 职场文书
监察建议书
2015/02/04 职场文书
2015年基层党建工作总结
2015/05/14 职场文书