Flask-WTF表单的使用方法


Posted in Python onJuly 12, 2019

flask_wtf是flask框架的表单验证模块,可以很方便生成表单,也可以当做json数据交互的验证工具,支持热插拔。

安装

pip install Flask-WTF

Flask-WTF其实是对wtforms组件的封装,使其支持对flask框架的热插拔。

简单使用

# app.py
from flask import Flask, current_app, request, render_template
from forms import MyForm

app = Flask(__name__,template_folder='static/html')
@app.route('/',methods=['GET','POST'])
def login():
  form = MyForm()
  if form.validate_on_submit():
    return 'OK'
  return render_template('forms/index.html', form=form)
if __name__ == '__main__':
  app.run(host='127.0.0.1', port=80, debug=True)

# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired

class MyForm(FlaskForm):
  name = StringField('name', validators=[DataRequired()])

# forms/index.html
<form method="POST" action="/">
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name(size=20) }}
<input type="submit" value="Go">
</form>

flask_wtf定义字段

flask_wtf完全使用wtforms组件的字段模型,wtforms对字段的定义在fields模块;又分为core和simple,core模块定义了普通使用的字段,simple在core模块的基础上扩展了一些字段,这些字段会自动进行字段级别的校验。

字段类型

# core.py
__all__ = (
  'BooleanField', 'DecimalField', 'DateField', 'DateTimeField', 'FieldList',
  'FloatField', 'FormField', 'IntegerField', 'RadioField', 'SelectField',
  'SelectMultipleField', 'StringField',
)

常用字段说明:

  • BooleanField:布尔类型,如Flask,True
  • StringField:字符串类型
  • DecimalField:小数点文本字段,如:‘1.23'
  • DateField:日期字段,格式:'%Y-%m-%d'
  • DateTimeField:日期字段,格式:'%Y-%m-%d %H:%M:%S'
  • FieldList:统一字段类型组成列表,如:FieldList(StringField('Name', [validators.required()]))
  • FloatField:浮点数类型
  • IntegerField:整形
  • SelectMultipleField:多选框
  • RadioField:单选框

simple.py

  • TextAreaField:文本域,可接受多行输入
  • PasswordField:密码字段,输入的不会直接在浏览器明文显示
  • FileField:上传文件,但不会处理验证文件,需要手动处理
  • HiddenField:隐藏字段
  • SubmitField:按钮
  • TextField:字符串类型的别名,弃用

表单定义

# 参数:
class UserAdminForm(FlaskForm):
  username = StringField(label='用户名', validators=[DataRequired(),Length(4,20)])
  password_hash = PasswordField(label='密码',validators=[DataRequired(),Length(4,20)])
  limit = SelectField(label='用户权限',
            choices=[('guest', '所有权限'),
                 ('operation', '可读可写不可删除'),
                 ('management', '可读不可写')],
            default='guest') # 权限

# 字段一般的参数
# label:字段的名字
# default:默认
# validators:字段的验证序列
# description:字段的描述
# choices:SelectField和他的子类有的字段,选择框,多选一

字段的验证序列

字段的参数validators可以指定提交表单的验证序列,按照从左到右的顺序,默认的可选验证在wtforms.validators模块,已经封装的验证方法有:

__all__ = (
  'DataRequired', 'data_required', 'Email', 'email', 'EqualTo', 'equal_to',
  'IPAddress', 'ip_address', 'InputRequired', 'input_required', 'Length',
  'length', 'NumberRange', 'number_range', 'Optional', 'optional',
  'Required', 'required', 'Regexp', 'regexp', 'URL', 'url', 'AnyOf',
  'any_of', 'NoneOf', 'none_of', 'MacAddress', 'mac_address', 'UUID'
)

模块中大小写有对应的方式,如DataRequired对应data_required。

  • DataRequired/data_required:验证数据是否真实存在,即不能为空,必须是非空白字符串,否则触发StopValidation错误。
  • InputRequired/input_required:和DataRequired的区别在于可以是空白字符串;
  • Required/required:data_required的别名
  • Email/email:验证符合邮件的格式,只有最基本的验证;
  • EqualTo/equal_to:比较两个字段的值,比如密码和确认密码,如果不相等就触发错误,equal_to(field,message),需要输入另一个字段的名字。
  • IPAddress/ip_address:验证是否是ip地址,默认验证IPV4地址。
  • MacAddress/mac_address:验证是否符合mac格式;
  • UUID:是否是uuid格式;
  • URL/url:验证是否符合url格式;
  • Regexp/regexp:用提供的正则表达式验证字段;Regexp(r"")
  • Length/length:设置字段值的长度,Length(min,max);
  • NumberRange/number_range:设置一个数字字段的取值范围,可以针对浮点数和小数;NumberRange(min,max)
  • Optional/optional:允许字段为空并停止验证;
  • NoneOf/none_of:将传入的数据和无效的比较,是抛出异常;Noneof(values).
  • Anyof/any_of:将传入的数据和预设的数据比较,不是异常。Anyof(values).

自定义字段验证

如果默认的验证序列不满足我们的要求,我们可以通过继承的方式自定义字段。

from wtforms.validators import DataRequired,Length,StopValidation
class NewStringField(StringField):
  """
  自定义一个新的字段
  """
  def pre_validate(self, form):
    """验证方法,在validators验证序列之前"""
    x:str = form.name.data
    if not x.startswith('g'):
      raise StopValidation("your data must be startswith 'g'")

  def post_validate(self, form, validation_stopped):
    """
    验证方法,在validators验证序列之后
    :param form:该字段所属的表单对象
    :param validation_stopped:前面验证序列的结果,True表示验证通过,False表示验证失败
    :return:
    """
    if not validation_stopped:
      raise ValueError("验证失败了!")
    pass

触发StopValidation异常会停止验证链;

自定义表单验证

一般来说,如果对表单有额外需要的验证,一般自定义表单的额外的验证方法而不是重新自定义新的字段,而form已经为我们提供了这种方法。

看Form对象的源码:

def validate(self):
  """
  Validates the form by calling `validate` on each field, passing any
  extra `Form.validate_<fieldname>` validators to the field validator.
  """
  extra = {}
  for name in self._fields:
    inline = getattr(self.__class__, 'validate_%s' % name, None)
    if inline is not None:
      extra[name] = [inline]

  return super(Form, self).validate(extra)

Form对象调用validate函数时会自动寻找validate_%s的方法添加到验证序列,并在原先字段的验证序列验证完毕后执行。

class MyForm(FlaskForm):
  name = StringField('name', validators=[DataRequired(), Length(4,20)])
  def validate_name(self, field):
    print(field.data)
    if hasattr(self, 'name') and len(self.name.data) > 5:
      print(self.name.data)
      return True
    raise ValidationError('超过5个字符')

# 在自定义的验证方法中,抛出异常使用ValidationError,validate会自动捕捉。

表单对象

flask_wtf推荐使用Form对象的子类FlaskForm代替,该对象提供了所有表单需要的属性和方法。那么Form对象是如何自动实现表单功能的呢?

分析FlaskForm对象源码:

class FlaskForm(Form):
  class Meta(DefaultMeta):
    def wrap_formdata(self, form, formdata):
      pass

  def __init__(self, formdata=_Auto, **kwargs):
    csrf_enabled = kwargs.pop('csrf_enabled', None)
    pass
  def is_submitted(self):
    pass
  def validate_on_submit(self):
    pass
  def hidden_tag(self, *fields):
    pass
  def validate(self):
    pass
  • FlaskForm内部定义了一个Meta类,该类添加csrf保护的一些方法,所以创建表单的时候一定要导入FlaskForm而不是Form.
  • is_submitted:检查是否有一个活跃的request请求;
  • validate_on_submit:调用is_submitted和validate方法,返回一个布尔值,用来判断表单是否被提交;
  • validate:字段级别的验证,每个字段都有一个validate方法,FlaskForm调用validate会对所有的字段调用validate方法验证,如果所有的验证都通过返回Ture,否则抛出异常。
  • hidden_tag:获取表单隐藏的字段;
  • wrap_formdata:获取request中的form,每次form对象初始化时会执行该函数从request获取form。

重要属性

  • form.data:字段名字和值组成的字典;
  • form.errors:验证失败的信息字典,在调用validate_on_submit方法后才有效;
  • form.name.data:字段name的值;
  • form.name.type:字段name的类型

常用场景

登录验证

# froms.py
class UserPasswordForm(FlaskForm):
  """
  登录提交的表单
  """
  username = StringField('User', validators=[DataRequired()])
  password = PasswordField('Password', validators=[DataRequired()])

# form.html
<form method="POST" action="/">
{{ form.csrf_token }}
{{ form.username.label }} {{ form.username(size=20) }}
{{ form.password.label }} {{ form.password }}
<input type="submit" value="Go">
</form>

# views.py
@app.route('/login',methods=['GET','POST'])
def login():
  form = UserPasswordForm()
  if form.validate_on_submit():
    # 验证表单
    if form.username.data == "xiaoming" and form.password.data == '123':
      return 'OK'
  return render_template('forms/index.html', form=form)

ajax请求转化表单

有时候我们没有html页面的表单,只有ajax请求的数据交互,但是想借用Form来定义接口和验证接收的数据,如果ajax的请求方法是('POST', 'PUT', 'PATCH', 'DELETE')中的一种,FlaskForm会自动从request对象中调用request.form和request.get_json()方法来接收数据,因此这种方式十分方便。注意:get方法不再其中。

# form.py
class MyForm(FlaskForm):
  name = StringField('name', validators=[DataRequired(), Length(4,20)])
# view.py
@app.route('/form',methods=['GET','POST'])
def form():
  if request.method != "GET":
    form = MyForm() # form会获取请求数据
    print(form.data)
    return 'ok'
  return ''
# test.py
import requests as req
import json

class ProTest():
  baseurl = 'http://127.0.0.1:80'
  def test_form(self):
    url = self.baseurl + '/form'
    rsp = req.post(url,json={'name':'hhhh'})
    # rsp = req.get(url,json={'name':'hhhh'})
if __name__ == '__main__':
  test = ProTest()
  test.test_form()

form启用csrf保护

默认csrf保护是开启的,只要在html文件中添加{{ form.csrf_token }},app必须设置SECRET_KEY参数。

# 禁用保护
form = Form(csrf_enabled=False)
# 或配置app时
WTF_CSRF_ENABLED = False

一般数据csrf保护

同理必须设置SECRET_KEY参数。

from flask_wtf.csrf import CsrfProtect
csrf = CsrfProtect()

def create_app():
  app = Flask(__name__)
  csrf.init_app(app)

# 模板中添加一个隐藏域
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<meta name="csrf-token" content="{{ csrf_token() }}">
# 如果是ajax请求,可以在脚本中
var csrftoken = "{{ csrf_token() }}"
# 然后每个请求添加 X-CSRFToken 头部

# 全局禁用csrf
WTF_CSRF_CHECK_DEFAULT = False

# 对一些视图跳过csrf检查
@csrf.exempt
@app.route('/foo', methods=('GET', 'POST'))
def my_handler():
  return 'ok'

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
在Python中使用lambda高效操作列表的教程
Apr 24 Python
Python处理字符串之isspace()方法的使用
May 19 Python
Python中使用装饰器来优化尾递归的示例
Jun 18 Python
图文详解python安装Scrapy框架步骤
May 20 Python
对Python 简单串口收发GUI界面的实例详解
Jun 12 Python
Python 使用 PyMysql、DBUtils 创建连接池提升性能
Aug 14 Python
python类的实例化问题解决
Aug 31 Python
python应用文件读取与登录注册功能
Sep 23 Python
python Shapely使用指南详解
Feb 18 Python
部署Django到阿里云服务器教程示例
Jun 03 Python
Python Matplotlib绘制条形图的全过程
Oct 24 Python
PYTHON使用Matplotlib去实现各种条形图的绘制
Mar 22 Python
解决Python中pandas读取*.csv文件出现编码问题
Jul 12 #Python
python的debug实用工具 pdb详解
Jul 12 #Python
Flask配置Cors跨域的实现
Jul 12 #Python
python调用webservice接口的实现
Jul 12 #Python
python3模拟实现xshell远程执行liunx命令的方法
Jul 12 #Python
Python实现蒙特卡洛算法小实验过程详解
Jul 12 #Python
教你如何编写、保存与运行Python程序的方法
Jul 12 #Python
You might like
php下获取Discuz论坛登录用户名、用户组、用户ID等信息的实现代码
2010/12/29 PHP
php Imagick获取图片RGB颜色值
2014/07/28 PHP
php去除数组中重复数据
2014/11/18 PHP
PHP实现微信模拟登陆并给用户发送消息的方法【文字,图片,图文】
2017/06/29 PHP
PHP 枚举类型的管理与设计知识点总结
2020/02/13 PHP
SOSO地图JS画出标注和中心点以html形式运行
2013/08/09 Javascript
javascript的alert box在java中如何显示多行
2014/05/18 Javascript
通过隐藏iframe实现无刷新上传文件操作
2016/03/16 Javascript
Bootstrap框架下下拉框select搜索功能
2020/03/26 Javascript
使用jQuery,Angular实现登录界面验证码详解
2017/04/27 jQuery
js CSS3实现卡牌旋转切换效果
2017/07/04 Javascript
写一个移动端惯性滑动&amp;回弹Vue导航栏组件 ly-tab
2018/03/06 Javascript
node打造微信个人号机器人的方法示例
2018/04/26 Javascript
vue使用laydate时间插件的方法
2018/11/14 Javascript
基于Vue插入视频的2种方法小结
2019/04/02 Javascript
layui 数据表格+分页+搜索+checkbox+缓存选中项数据的方法
2019/09/21 Javascript
vue 检测用户上传图片宽高的方法
2020/02/06 Javascript
js实现带箭头的进度流程
2020/03/26 Javascript
nuxt.js写项目时增加错误提示页面操作
2020/11/05 Javascript
[00:05]ChinaJoy现场 DOTA2玩家高呼“CN DOTA BEST DOTA”
2019/08/04 DOTA
从零学Python之引用和类属性的初步理解
2014/05/15 Python
python私有属性和方法实例分析
2015/01/15 Python
python机器学习之贝叶斯分类
2018/03/26 Python
python3字符串操作总结
2019/07/24 Python
Python列表操作方法详解
2020/02/09 Python
pyspark给dataframe增加新的一列的实现示例
2020/04/24 Python
浅谈tensorflow使用张量时的一些注意点tf.concat,tf.reshape,tf.stack
2020/06/23 Python
CSS3制作精致的照片墙特效
2016/06/07 HTML / CSS
俄罗斯奢侈品牌衣服、鞋子和配饰的在线商店:INTERMODA
2020/07/17 全球购物
叙述DBMS对数据控制功能有哪些
2016/06/12 面试题
网络宣传方案
2014/03/15 职场文书
机关党建工作汇报材料
2014/08/20 职场文书
刑事和解协议书范本
2014/11/19 职场文书
会计专业求职信范文
2015/03/19 职场文书
交通安全温馨提示语
2015/07/14 职场文书
《三国志》赏析
2019/08/27 职场文书