简介

WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证。
安装: pip3 install wtforms

用户登录注册示例

1. 用户登录

当用户登录时候,需要对用户提交的用户名和密码进行多种格式校验。如:

  • 用户不能为空;用户长度必须大于6;
  • 密码不能为空;密码长度必须大于12;密码必须包含 字母、数字、特殊字符等(自定义正则);
############################## app.py #############################
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core    # 字段大部分都在core html5 simple 中
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgetsapp = Flask(__name__, template_folder='templates')
app.debug = Trueclass LoginForm(Form):name = simple.StringField(label='用户名',     # 显示名称validators=[       # 校验validators.DataRequired(message='用户名不能为空.'),validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')],widget=widgets.TextInput(),           # 插件render_kw={'class': 'form-control'}   # 渲染)pwd = simple.PasswordField(label='密码',validators=[validators.DataRequired(message='密码不能为空.'),validators.Length(min=8, message='用户名长度必须大于%(min)d'),validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}",message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')],widget=widgets.PasswordInput(),render_kw={'class': 'form-control'})@app.route('/login', methods=['GET', 'POST'])
def login():if request.method == 'GET':form = LoginForm()            # 实例化return render_template('login.html', form=form)else:form = LoginForm(formdata=request.form)           # 校验if form.validate():print('用户提交数据通过格式验证,提交的值为:', form.data)else:print(form.errors)return render_template('login.html', form=form)if __name__ == '__main__':app.run()
############################## login.html #############################
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>登录</h1>
<form method="post"><!--<input type="text" name="name">--><p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p><!--<input type="password" name="pwd">--><p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p><input type="submit" value="提交">
</form>
</body>
</html>

2. 用户注册

注册页面需要让用户输入:用户名、密码、密码重复、性别、爱好等。

############################## app.py #############################
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgetsapp = Flask(__name__, template_folder='templates')
app.debug = Trueclass RegisterForm(Form):name = simple.StringField(label='用户名',validators=[validators.DataRequired()],widget=widgets.TextInput(),render_kw={'class': 'form-control'},default='alex')pwd = simple.PasswordField(label='密码',validators=[validators.DataRequired(message='密码不能为空.')],widget=widgets.PasswordInput(),render_kw={'class': 'form-control'})pwd_confirm = simple.PasswordField(label='重复密码',validators=[validators.DataRequired(message='重复密码不能为空.'),validators.EqualTo('pwd', message="两次密码输入不一致")],widget=widgets.PasswordInput(),render_kw={'class': 'form-control'})email = html5.EmailField(label='邮箱',validators=[validators.DataRequired(message='邮箱不能为空.'),validators.Email(message='邮箱格式错误')],widget=widgets.TextInput(input_type='email'),render_kw={'class': 'form-control'})gender = core.RadioField(label='性别',choices=((1, '男'),(2, '女'),),coerce=int   # 会自动转化为int)city = core.SelectField(label='城市',choices=(('bj', '北京'),('sh', '上海'),))hobby = core.SelectMultipleField(label='爱好',choices=((1, '篮球'),(2, '足球'),),coerce=int)favor = core.SelectMultipleField(label='喜好',choices=((1, '篮球'),(2, '足球'),),widget=widgets.ListWidget(prefix_label=False),option_widget=widgets.CheckboxInput(),coerce=int,default=[1, 2])def __init__(self, *args, **kwargs):super(RegisterForm, self).__init__(*args, **kwargs)self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))def validate_pwd_confirm(self, field):  # 钩子函数,filed 代表要检验的字段,self 代表所有的数据"""自定义pwd_confirm字段规则,例:与pwd字段是否一致:param field: :return: """# 最开始初始化时,self.data中已经有所有的值if field.data != self.data['pwd']:# raise validators.ValidationError("密码不一致") # 继续后续验证,验证的是本字段raise validators.StopValidation("密码不一致")  # 不再继续后续验证,验证的是本字段@app.route('/register', methods=['GET', 'POST'])
def register():if request.method == 'GET':form = RegisterForm(data={'gender': 1})   # data 为form 传值return render_template('register.html', form=form)else:form = RegisterForm(formdata=request.form)if form.validate():print('用户提交数据通过格式验证,提交的值为:', form.data)else:print(form.errors)return render_template('register.html', form=form)if __name__ == '__main__':app.run()
############################## login.html #############################
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>用户注册</h1>
<form method="post" novalidate style="padding:0  50px">{% for item in form %}<p>{{item.label}}: {{item}} {{item.errors[0] }}</p>{% endfor %}<input type="submit" value="提交">
</form>
</body>
</html>

3. meta

使用WTFrom 提供的 csrf_token,一般不怎么用

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect, session
from wtforms import Form
from wtforms.csrf.core import CSRF
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
from hashlib import md5app = Flask(__name__, template_folder='templates')
app.debug = Trueclass MyCSRF(CSRF):  # 继承CSRF,实现自己的规则"""Generate a CSRF token based on the user's IP. I am probably not verysecure, so don't use me."""def setup_form(self, form):self.csrf_context = form.meta.csrf_context()self.csrf_secret = form.meta.csrf_secretreturn super(MyCSRF, self).setup_form(form)def generate_csrf_token(self, csrf_token):gid = self.csrf_secret + self.csrf_contexttoken = md5(gid.encode('utf-8')).hexdigest()return tokendef validate_csrf_token(self, form, field):print(field.data, field.current_token)if field.data != field.current_token:raise ValueError('Invalid CSRF')class TestForm(Form):name = html5.EmailField(label='用户名')pwd = simple.StringField(label='密码')class Meta:# -- CSRF# 是否自动生成CSRF标签csrf = True# 生成CSRF标签namecsrf_field_name = 'csrf_token'# 自动生成标签的值,加密用的csrf_secretcsrf_secret = 'xxxxxx'# 自动生成标签的值,加密用的csrf_contextcsrf_context = lambda x: request.url# 生成和比较csrf标签csrf_class = MyCSRF# -- i18n# 是否支持本地化# locales = Falselocales = ('zh', 'en')# 是否对本地化进行缓存cache_translations = True# 保存本地化缓存信息的字段translations_cache = {}@app.route('/index/', methods=['GET', 'POST'])
def index():if request.method == 'GET':form = TestForm()else:form = TestForm(formdata=request.form)if form.validate():print(form)return render_template('index.html', form=form)if __name__ == '__main__':app.run()

其他:

1. metaclass

第一阶段

class MyType(type):def __init__(self, *args, **kwargs):print('MyType创建类',self)super(MyType, self).__init__(*args, **kwargs)def __call__(self, *args, **kwargs):obj = super(MyType, self).__call__(*args, **kwargs)print('类创建对象', self, obj)return objclass Foo(object,metaclass=MyType):user = 'flepeng'age = 18obj = Foo()

第二阶段

class MyType(type):def __init__(self, *args, **kwargs):super(MyType, self).__init__(*args, **kwargs)def __call__(cls, *args, **kwargs):v = dir(cls)obj = super(MyType, cls).__call__(*args, **kwargs)return objclass Foo(MyType('MyType', (object,), {})):user = 'wupeiqi'age = 18obj = Foo()

第三阶段

class MyType(type):def __init__(self, *args, **kwargs):super(MyType, self).__init__(*args, **kwargs)def __call__(cls, *args, **kwargs):v = dir(cls)obj = super(MyType, cls).__call__(*args, **kwargs)return objdef with_metaclass(arg,base):return MyType('MyType', (base,), {})class Foo(with_metaclass(MyType,object)):user = 'wupeiqi'age = 18obj = Foo()

2. 实例化流程分析

# 源码流程1. 执行type的 __call__ 方法,读取字段到静态字段 cls._unbound_fields 中; meta类读取到cls._wtforms_meta中2. 执行构造方法a. 循环cls._unbound_fields中的字段,并执行字段的bind方法,然后将返回值添加到 self._fields[name] 中。即:_fields = {name: wtforms.fields.core.StringField(),}PS:由于字段中的__new__方法,实例化时:name = simple.StringField(label='用户名'),创建的是UnboundField(cls, *args, **kwargs),当执行完bind之后,才变成执行 wtforms.fields.core.StringField()b. 循环_fields,为对象设置属性for name, field in iteritems(self._fields):# Set all the fields to attributes so that they obscure the class# attributes with the same names.setattr(self, name, field)c. 执行process,为字段设置默认值:self.process(formdata, obj, data=data, **kwargs)优先级:obj,data,formdata;再循环执行每个字段的process方法,为每个字段设置值:for name, field, in iteritems(self._fields):if obj is not None and hasattr(obj, name):field.process(formdata, getattr(obj, name))elif name in kwargs:field.process(formdata, kwargs[name])else:field.process(formdata)执行每个字段的process方法,为字段的data和字段的raw_data赋值def process(self, formdata, data=unset_value):self.process_errors = []if data is unset_value:try:data = self.default()except TypeError:data = self.defaultself.object_data = datatry:self.process_data(data)except ValueError as e:self.process_errors.append(e.args[0])if formdata:try:if self.name in formdata:self.raw_data = formdata.getlist(self.name)else:self.raw_data = []self.process_formdata(self.raw_data)except ValueError as e:self.process_errors.append(e.args[0])try:for filter in self.filters:self.data = filter(self.data)except ValueError as e:self.process_errors.append(e.args[0])d. 页面上执行print(form.name) 时,打印标签因为执行了:字段的 __str__ 方法字符的 __call__ 方法self.meta.render_field(self, kwargs)def render_field(self, field, render_kw):other_kw = getattr(field, 'render_kw', None)if other_kw is not None:render_kw = dict(other_kw, **render_kw)return field.widget(field, **render_kw)执行字段的插件对象的 __call__ 方法,返回标签字符串

3. 验证流程分析

a. 执行form的validate方法,获取钩子方法def validate(self):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)b. 循环每一个字段,执行字段的 validate 方法进行校验(参数传递了钩子函数)def validate(self, extra_validators=None):self._errors = Nonesuccess = Truefor name, field in iteritems(self._fields):if extra_validators is not None and name in extra_validators:extra = extra_validators[name]else:extra = tuple()if not field.validate(self, extra):success = Falsereturn successc. 每个字段进行验证时候字段的pre_validate 【预留的扩展】字段的_run_validation_chain,对正则和字段的钩子函数进行校验字段的post_validate【预留的扩展】
################################## class FormMeta ###############################
class FormMeta(type):# 创建类时,调用的方法def __init__(cls, name, bases, attrs):type.__init__(cls, name, bases, attrs)cls._unbound_fields = Nonecls._wtforms_meta = None# 实例化类时调用的方法def __call__(cls, *args, **kwargs):if cls._unbound_fields is None:fields = []for name in dir(cls):if not name.startswith('_'):unbound_field = getattr(cls, name)if hasattr(unbound_field, '_formfield'):   # 只要是继承自File,_formfield = Truefields.append((name, unbound_field))   # 将字段添加至fields fields.sort(key=lambda x: (x[1].creation_counter, x[0]))cls._unbound_fields = fields                       # _unbound_fields =[字段对应的示例,]if cls._wtforms_meta is None:bases = []for mro_class in cls.__mro__:if 'Meta' in mro_class.__dict__:bases.append(mro_class.Meta)               # 将所有的Meta添加至bases中cls._wtforms_meta = type('Meta', tuple(bases), {}) # 创建一个新的Meta,并继承自找到的Meta列表return type.__call__(cls, *args, **kwargs)             # 执行type的call方法,前面的都是派生的
################################## class Form ###############################
# class Form(BaseForm , classmate=FormMeta)
class Form(with_metaclass(FormMeta, BaseForm)):Meta = DefaultMetadef __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):meta_obj = self._wtforms_meta()if meta is not None and isinstance(meta, dict):meta_obj.update_values(meta)super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)for name, field in iteritems(self._fields):setattr(self, name, field)self.process(formdata, obj, data=data, **kwargs)def __setitem__(self, name, value):raise TypeError('Fields may not be added to Form instances, only classes.')def __delitem__(self, name):del self._fields[name]setattr(self, name, None)def __delattr__(self, name):if name in self._fields:self.__delitem__(name)else:unbound_field = getattr(self.__class__, name, None)if unbound_field is not None and hasattr(unbound_field, '_formfield'):setattr(self, name, None)else:super(Form, self).__delattr__(name)def validate(self):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)
################################## class BaseForm ###############################
class BaseForm(object):def __init__(self, fields, prefix='', meta=DefaultMeta()):if prefix and prefix[-1] not in '-_;:/.':prefix += '-'self.meta = metaself._prefix = prefixself._errors = Noneself._fields = OrderedDict()if hasattr(fields, 'items'):fields = fields.items()translations = self._get_translations()extra_fields = []if meta.csrf:self._csrf = meta.build_csrf(self)extra_fields.extend(self._csrf.setup_form(self))for name, unbound_field in itertools.chain(fields, extra_fields):options = dict(name=name, prefix=prefix, translations=translations)field = meta.bind_field(self, unbound_field, options)self._fields[name] = field

Flask 第三方组件之 WTForms相关推荐

  1. 4.flask第三方组件

    1.flask-session的使用 在flask中,有一个app.session_interface = SecureCookieSessionInterface(),也就是存session,调用o ...

  2. Flask 第三方组件之 login

    在了解使用Flask来实现用户认证之前,我们首先要明白用户认证的原理.假设现在我们自己去实现用户认证,需要做哪些事情呢? 首先,登录.用户能够输入用户名和密码进行登录,所以需要网页和表单,实现用户输入 ...

  3. Flask 第三方组件之 Migrate

    flask-migrate是flask的一个扩展模块,主要是扩展数据库表结构的.类似于Django的python manage.py migrate 官方文档: http://flask-migrat ...

  4. Flask 第三方组件之 script

    Flask Script扩展提供向Flask插入外部脚本的功能,包括运行一个开发用的服务器,一个定制的Python shell,设置数据库的脚本,cronjobs,及其他运行在web应用之外的命令行任 ...

  5. Flask 第三方组件之 SQLAlchemy

    一.介绍 SQLAlchemy是一个基于Python实现的ORM框架.该框架建立在 DB API之上,使用关系对象映射进行数据库操作,简言之便是:将类和对象转换成SQL,然后使用数据API执行SQL并 ...

  6. Web后端学习笔记 Flask(8) WTForms 表单验证,文件上传

    Flask-WTF是简化了WTForms操作的一个第三方库.WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板.同时还包含一些其他的功能.例如CSRF保护,文件上传等功能,安装fl ...

  7. iOS 项目中用到的一些开源库和第三方组件

    iOS 项目中用到的一些 iOS 开源库和第三方组件 分享一下我目前所在公司 iOS 项目中用到的一些 iOS 开源库和第三方组件, 感谢开源, 减少了我们的劳动力, 节约了我们大量的时间, 让我们有 ...

  8. React Native 项目常用第三方组件汇总

    React Native 项目常用第三方组件汇总 https://www.jianshu.com/p/d9cd9a868764?utm_campaign=maleskine&utm_conte ...

  9. android多线程下载原理,安卓多线程断点续传下载功能(靠谱第三方组件,原理demo)...

    一,原生的DownloadManager 从Android 2.3(API level 9)开始,Android以Service的方式提供了全局的DownloadManager来系统级地优化处理长时间 ...

最新文章

  1. 在LNMP下用nginx 1.4.7配置nagios监控
  2. 一个具有多模型融合能力的网络或许是这样的
  3. 研究项目: JBoss架构分析
  4. 利用邮箱实现数据通信
  5. 使用secondary sort实现数据关联 完整示例代码
  6. sql server 2008安装需要一直重启。但重启后又没有达到效果。
  7. 寻址(实模式和保护模式)
  8. ·MySQL数据库管理(SQL操作命令,解决忘记密码,设置用户权限)
  9. leetcode题解677-键值映射
  10. android 炫酷时间轴,这38款超级炫酷的时间轴特效代码案例,总有一款是你需要的...
  11. 已知圆心 坐标和一点坐标和角度 就之后的坐标_LaTeX 中绘制多个相交椭圆(对起始角度与终止角度的思考)...
  12. keepalived+LVS 详解(2) -- keepalived.conf解析
  13. 打印纸张尺寸换算_纸张开本和尺寸对照表!
  14. Linux brctl 命令,虚拟网络设备 LinuxBridge 管理工具
  15. 常见的视频格式以及视频编解码标准(附带OpenCV实现视频的读取与保存)
  16. 自定义万能回弹插值器 BounceInterpolator
  17. 日文輸入法学习和日文键盘图
  18. 红警conquer.mix文件
  19. 糖尿病11年的隔壁老王
  20. 毕业设计 Stm32云平台的智能病房监控系统

热门文章

  1. 钱与命的八句真实话(图)
  2. 代码之美——Doom3源代码赏析
  3. Java List<T>去重方法,引用类型集合去重
  4. referer参数和addslashes()函数的骚路子
  5. jQuery BreakingNews 间歇滚动
  6. Mutual and feedback(互评与反馈)
  7. 领域驱动设计和实践(转:http://kb.cnblogs.com/page/112298/)
  8. 自动备份多个MOSS站点集的脚本
  9. 递归实现进制转换(C++版)
  10. Mybatis 详解--- 一级缓存、二级缓存