涉及到的插件和包有Flask-WTF,WTForms。内容有表单的创建使用流程,一些最佳实践,还有在页面显示提示消息的简单方式,配合Flask内置的 flash()
。
Flask的requset对象包含了client端发送过来的所有请求,在request.form中就有POST方法提交过来的表单数据。直接使用这些数据可以搞定表单的操作,不过不方便,于是有了Flask-WTF这个插件,它将WTForms这个包嵌入Flask里,简化Flask下的使用。pip安装会把插件的以来也安装进来:
1 | pip install flask – wtf |
WTForms应该也同时被安装了。
CSRF的原理不具体讲了,很简单,感兴趣直接网上搜即可。
Flask-WTF默认提供对CSRF的保护。应用里需要设置一个加密用的key,Flask-WTF利用这个key生成一个加密的记号来验证request带过来的表单数据。看看实例:
1 2 | app = Flask ( __name__ ) app . config [ ‘SECRET_KEY’ ] = ‘www.trustauth.cn’ |
app.config
是应用保存配置的一个字典。可以直接在字典里增加配置。SECRET_KEY
这个配置变量被Flask和一些第三方插件使用,对不同的应用配置不同的值增加点可靠性。
另外,这个值最好放到环境变量里,直接写到代码里不太好。
使用Flask-WTF的时候,每一个表单都是类的形式,这个类需要继承自Form。这个类里定义一些代表表单各类域的对象,每个对象可以有多个验证器(validators)。验证器可以确保用户的输入是有效的。
原例子:
1 2 3 4 5 6 7 | from flask . ext . wtf import Form from wtforms import StringField , SubmitField from wtforms . validators import Required class NameForm ( Form ) : name = StringField ( ‘What is your name?’ , validators = [ Required ( ) ] ) submit = SubmitField ( ‘Submit’ ) |
表单中的域在类中都定义成类变量。上例中,NameForm
类里有文本域name
和提交按钮submit
两个。StringField
代表有type="text"
属性的<input>
元素。SubmitField
代表有type="submit"
属性的<input>
元素。构造器的第一个参数是后续渲染表单时候用到的标签(label
)。
下例是一个带有文本域和提交按钮的表单例子:
1 2 3 4 5 6 7 8 9 | from flask_wtf import Form from wtforms import StringField , BooleanField , PasswordField , SubmitField from wtforms . validators import DataRequired class LoginForm ( Form ) : openid = StringField ( ‘openid’ , validators = [ DataRequired ( ) ] ) remember_me = BooleanField ( ‘remember_me’ , default = False ) password = PasswordField ( ‘password’ , validators = [ DataRequired ( ) ] ) submit = SubmitField ( ‘submit’ ) |
表单中的域在类中都定义成类变量。上例中,LoginForm
类里有字符串域openid,
复选框remember_me, 密码域password,提交按钮submit
。分别代表小面信息:
1 2 3 4 | <input id = “openid” name = “openid” type = “text” value = “” > <input id = “remember_me” name = “remember_me” type = “checkbox” value = “y” > <input id = “password” name = “password” type = “password” value = “” > <input id = “submit” name = “submit” type = “submit” value = “submit” > |
构造器的第一个参数是后续渲染表单时候用到的标签(label
)。
在StringField
里的validators
参数定义了一些验证器,这些验证器会在用户提交数据前检查数据是否有效。Required
验证器确保提交的内容不能为空。
WTForms提供的各种HTML域:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 域类型 含义 StringField 文本 TextAreaField 多行文本 PasswordField 密码类文本 HiddenField 隐藏文本 DateField 接收给定格式的 datetime . datevalue 的文本 DateTimeField 接收给定格式的 datetime . datetimevalue 的文本 T IntegerField 接收整数的文本 DecimalField 接收 decimal . Decimal类型值的文本 FloatField 接收浮点类型值的文本 BooleanField 选是否的复选框 RadioField 包含多个互斥选项的复选框 SelectField 下拉菜单 SelectMultipleField 可多选的下拉菜单 FileField 文件上传 SubmitField 提交 FormField 讲一个表单作为域放入另一个表单里 FieldList 一组给定类型的域 |
WTForms提供的各种验证器:
1 2 3 4 5 6 7 8 9 10 11 12 | Validator Description Email 邮箱格式 EqualTo 比较两个域的值,例如在要求输入两次密码的时候 IPAddress IPv4 地址 Length 按字符串的长度验证 NumberRange 输入数字需在某范围内 Optional 允许不填,不填的时候就忽略其他验证器 Required 必填 Regexp 通过一个正则表达式验证 URL URL格式 AnyOf 属于一组可能值中的一个 NoneOf 不属于一组可能值中的任何一个 |
表单的各类域在模板中渲染时表现为可调用的对象。假设将一个NameForm
的实例name
作为参数传入模板。
1 2 3 4 | < form method = “POST” > { { form . name . label } } { { form . name ( ) } } { { form . submit ( ) } } < / form > |
这样渲染出来的页面不美观,可以尝试改进下,在调用里传入一些参数,这些参数都会被转化为这个域的属性。然后你可以用CSS自己搞定美化问题:
1 2 3 4 | < form method = “POST” > { { form . name . label } } { { form . name ( id = ‘my-text-field’ ) } } { { form . submit ( ) } } < / form > |
上述方式显然很累,之前加入了Bootstrap的支持,Flask-Bootstrap插件其实也对Flask-WTF创建的表单有高层接口的支持,可以用Bootstrap来修饰一下。然后表单的模板就可以简单写成:
1 2 | { % import “bootstrap/wtf.html” as wtf % } { { wtf . quick_form ( form ) } } |
从其他模板import个函数进来之前提到过,wtf.quick_form
函数接受一个Flask-WTF的表单,然后用Bootstrap默认的样式渲染。
现在,首页index.html
已经改为:
1 2 3 4 5 6 7 8 9 | { % extends “base.html” % } { % import “bootstrap/wtf.html” as wtf % } { % block title % } Flasky { % endblock % } { % block page_content % } < div class = “page-header” > < h1 > Hello , { % if name % } { { name } } { % else % } Stranger { % endif % } ! < / h1 > < / div > { { wtf . quick_form ( form ) } } { % endblock % } |
这里还用了一个if else结构,如果传入了name
,就显示传入的值,否则就显示Stranger。
表单的各类域在模板中渲染时表现为可调用的对象。假设将一个LoginForm
的实例openid作为参数传入模板。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | < form action = “” method = “post” name = “login” > { { form . hidden_tag ( ) } } { { form . openid . label } } { { form . openid ( size = 80 ) } } { % for error in form . openid . errors % } < span style = “color: red;” > { { error } } < / span > { % endfor % } < br > 密码 : { { form . password } } { % for error in form . password . errors % } < span style = “color: red;” > { { error } } < / span > { % endfor % } { { form . remember_me } } Remeber Me < / p > 提交 : { { form . submit ( value = ‘登录’ ) } } < / form > |
修改hello.py,在index()
里处理表单数据。
1 2 3 4 5 6 7 8 | @ app . route ( ‘/’ , methods = [ ‘GET’ , ‘POST’ ] ) def index ( ) : name = None form = NameForm ( ) if form . validate_on_submit ( ) : name = form . name . data form . name . data = ” return render_template ( ‘index.html’ , form = form , name = name ) |
可以注意到,在app.route
装饰器增加了methods
参数,这里是把index()
注册为GET和POST请求的处理者。如果不提供methods
这个参数,试图函数默认只处理GET请求。
这里对index()
增加视图函数对POST请求的支持是必须的,因为用户的提交操作使用POST请求更方便处理。使用GET请求来提交表单也可以,但是GET请求的数据都是附加在URL后面作为请求字符串,在浏览器的地址栏可以看到。由此,以及一些其他原因,表单的提交通常都是用POST请求完成的。
继续看改动后的代码,form.validate_on_submit()
这个方法,只在用户提交了数据并且数据通过验证器的检查之后,才返回True,其他时候都返回False。用这个方法判断是否对模板进行处理。
看下一般处理流程。用户第一次访问这个应用,使用的是GET请求,不带数据,form.validate_on_submit()
这个方法返回False,return就返回一个空白的表单,name
值是None。
用户提交了表单后,sercer收到携带数据的POST请求,form.validate_on_submit()
这个方法会启动之前设置的Required()
验证器,这里name不为空就通过验证,form.validate_on_submit()
返回True。然后提取出表单数据保存下来,把表单数据清空。再把name和表单传入render_template()
,重新渲染的模板就有了变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 | @ app . route ( ‘/login’ , methods = [ ‘GET’ , ‘POST’ ] ) def login ( ) : form = LoginForm ( ) print form . openid print form . remember_me print form . password print form . submit if form . validate_on_submit ( ) : flash ( ‘Login requested for OpenID=”‘ + form . openid . data + ‘”, remember_me=’ + str ( form . remember_me . data ) + ‘ password: ‘ ) return redirect ( ‘/index’ ) return render_template ( ‘login.html’ , title = ‘Sign In’ , form = form ) |
文章转载来自:trustauth.cn