涉及到的插件和包有Flask-WTF,WTForms。内容有表单的创建使用流程,一些最佳实践,还有在页面显示提示消息的简单方式,配合Flask内置的 flash()。

涉及到的插件和包有Flask-WTF,WTForms。内容有表单的创建使用流程,一些最佳实践,还有在页面显示提示消息的简单方式,配合Flask内置的 flash()。 Flask的requset对象包含了client端发送过来的所有请求,在request.form中就有POST方法提交过来的表单数据。直接使用这些数据可以搞定表单的操作,不过不方便,于是有了Flask-WTF这个插件,它将WTForms这个包嵌入Flask里,简化Flask下的使用。pip安装会把插件的以来也安装进来:

pip install flask-wtf 1 pip install flask-wtf WTForms应该也同时被安装了。 跨站请求伪造(Cross-Site Request Forgery,CSRF) 保护

CSRF的原理不具体讲了,很简单,感兴趣直接网上搜即可。 Flask-WTF默认提供对CSRF的保护。应用里需要设置一个加密用的key,Flask-WTF利用这个key生成一个加密的记号来验证request带过来的表单数据。看看实例:

app = Flask(name) app.config['SECRET_KEY'] = 'www.ttlsa.com' 1 2 app = Flask(name) app.config['SECRET_KEY'] = 'www.ttlsa.com' app.config 是应用保存配置的一个字典。可以直接在字典里增加配置。SECRET_KEY这个配置变量被Flask和一些第三方插件使用,对不同的应用配置不同的值增加点可靠性。 另外,这个值最好放到环境变量里,直接写到代码里不太好。 表单类

使用Flask-WTF的时候,每一个表单都是类的形式,这个类需要继承自Form。这个类里定义一些代表表单各类域的对象,每个对象可以有多个验证器(validators)。验证器可以确保用户的输入是有效的。 原例子:

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') 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)。 下例是一个带有文本域和提交按钮的表单例子:

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') 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。分别代表小面信息:

<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"> 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域:

域类型 含义 StringField 文本 TextAreaField 多行文本 PasswordField 密码类文本 HiddenField 隐藏文本 DateField 接收给定格式的 datetime.datevalue 的文本 DateTimeField 接收给定格式的 datetime.datetimevalue 的文本T IntegerField 接收整数的文本 DecimalField 接收decimal.Decimal类型值的文本 FloatField 接收浮点类型值的文本 BooleanField 选是否的复选框 RadioField 包含多个互斥选项的复选框 SelectField 下拉菜单 SelectMultipleField 可多选的下拉菜单 FileField 文件上传 SubmitField 提交 FormField 讲一个表单作为域放入另一个表单里 FieldList 一组给定类型的域 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提供的各种验证器:

Validator Description Email 邮箱格式 EqualTo 比较两个域的值,例如在要求输入两次密码的时候 IPAddress IPv4 地址 Length 按字符串的长度验证 NumberRange 输入数字需在某范围内 Optional 允许不填,不填的时候就忽略其他验证器 Required 必填 Regexp 通过一个正则表达式验证 URL URL格式 AnyOf 属于一组可能值中的一个 NoneOf 不属于一组可能值中的任何一个 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作为参数传入模板。

<form method="POST"> {{ form.name.label }} {{ form.name() }} {{ form.submit() }} </form> 1 2 3 4 <form method="POST"> {{ form.name.label }} {{ form.name() }} {{ form.submit() }} </form> 这样渲染出来的页面不美观,可以尝试改进下,在调用里传入一些参数,这些参数都会被转化为这个域的属性。然后你可以用CSS自己搞定美化问题:

<form method="POST"> {{ form.name.label }} {{ form.name(id='my-text-field') }} {{ form.submit() }} </form> 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来修饰一下。然后表单的模板就可以简单写成:

{% import "bootstrap/wtf.html" as wtf %} {{ wtf.quick_form(form) }} 1 2 {% import "bootstrap/wtf.html" as wtf %} {{ wtf.quick_form(form) }} 从其他模板import个函数进来之前提到过,wtf.quick_form函数接受一个Flask-WTF的表单,然后用Bootstrap默认的样式渲染。 现在,首页index.html已经改为:

{% 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 %} 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作为参数传入模板。

<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 %}
        &lt;span style=&quot;color: red;&quot;&gt;{{ error }}&lt;/span&gt;
    {% endfor%}
    {{form.remember_me}} Remeber Me&lt;/p&gt;
    提交:
    {{ form.submit(value='登录') }}

</form> 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 %}
        &lt;span style=&quot;color: red;&quot;&gt;{{ error }}&lt;/span&gt;
    {% endfor%}
    {{form.remember_me}} Remeber Me&lt;/p&gt;
    提交:
    {{ form.submit(value='登录') }}

</form>

视图函数中的表单处理

修改hello.py,在index()里处理表单数据。

@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) 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。用这个方法判断是否对模板进行处理。

tags: flask