python flask 表单处理Flask-WTF
涉及到的插件和包有Flask-WTF,WTForms。内容有表单的创建使用流程,一些最佳实践,还有在页面显示提示消息的简单方式,配合Flask内置的 flash()
。
Flask的requset对象包含了client端发送过来的所有请求,在request.form中就有POST方法提交过来的表单数据。直接使用这些数据可以搞定表单的操作,不过不方便,于是有了Flask-WTF这个插件,它将WTForms这个包嵌入Flask里,简化Flask下的使用。pip安装会把插件的以来也安装进来:
1
|
pip
install
flask
-
wtf
|
WTForms应该也同时被安装了。
跨站请求伪造(Cross-Site Request Forgery,CSRF) 保护
CSRF的原理不具体讲了,很简单,感兴趣直接网上搜即可。
Flask-WTF默认提供对CSRF的保护。应用里需要设置一个加密用的key,Flask-WTF利用这个key生成一个加密的记号来验证request带过来的表单数据。看看实例:
1
2
|
app
=
Flask
(
__name__
)
app
.
config
[
'SECRET_KEY'
]
=
'www.ttlsa.com'
|
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
)
|
成长的对话版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!