wtforms组件使用实例及源码解析
WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证。
WTforms作用:当网站中需要用到表单时,WTForms变得很有效。应该把表单定义为类,作为单独的一个模块。
创建表单: 创建表单时,通常是创建一个Form的子类,表单的中的字段作为类的属性。
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 widgets app = Flask(__name__, template_folder='templates')
app.debug = True class MyValidator(object): # 自定义验证器
def __init__(self,message):
self.message = message def __call__(self, form, field):
# print(field.data)
if field.data == '王浩':
return None
raise validators.StopValidation(self.message) class LoginForm(Form):
name = simple.StringField(
label='用户名',
validators=[
# MyValidator(message='用户名必须等于王浩')
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) # ########################### 用户注册 ##########################
class 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
)
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):
"""
自定义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})
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()
注册登录页面自定义表单
<!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>
注册.html
源码分析
将按照代码的执行顺序分析
1.当定义好一个自定义的Form类,项目加载Form类所在模块,代码都做了什么?
class 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'}
)
自定义类继承了Form类,看一下Form类
class Form(with_metaclass(FormMeta, BaseForm)):
pass def with_metaclass(meta, base=object):
return meta("NewBase", (base,), {}) # 那么相当于Form继承了FormMeta("NewBase",(BaseForm,),{} ) 使用 FormMeta元类创建的对象让Form继承,则Form的元类也被指定为FormMeta class FormMeta(type):
def __init__(cls, name, bases, attrs):
type.__init__(cls, name, bases, attrs)
cls._unbound_fields = None
cls._wtforms_meta = None
则 FormMeta创建RegisterForm为了RegisterForm产生了两个类变量
RegisterForm._unbound_fields = None
RegisterForm._wtforms_meta = None
再看类变量name 和 pwd 的字段是什么
class StringField(Field):
"""
This field is the base for most of the more complicated fields, and
represents an ``<input type="text">``.
"""
widget = widgets.TextInput() def process_formdata(self, valuelist):
if valuelist:
self.data = valuelist[0]
elif self.data is None:
self.data = '' def _value(self):
return text_type(self.data) if self.data is not None else '' class Field(object):
"""
Field base class
"""
errors = tuple()
process_errors = tuple()
raw_data = None
validators = tuple()
widget = None
_formfield = True
_translations = DummyTranslations()
do_not_call_in_templates = True # Allow Django 1.4 traversal def __new__(cls, *args, **kwargs):
if '_form' in kwargs and '_name' in kwargs:
return super(Field, cls).__new__(cls)
else:
return UnboundField(cls, *args, **kwargs) def __init__(self, label=None, validators=None, filters=tuple(),
description='', id=None, default=None, widget=None,
render_kw=None, _form=None, _name=None, _prefix='',
_translations=None, _meta=None):
pass
StringField源码
此时RegisterForm的类变量为
RegisterForm._unbound_fields = None
RegisterForm._wtforms_meta = None
RegisterForm.name = UnboundField(simple.StringField)
RegisterForm.pwd = UnboundField(simple.PasswordField)
print(RegisterForm.name,type(RegisterForm.name))
“”“
<UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x000001C76FE778D0>], 'widget': <wtforms.widgets.core.TextInput object at 0x000001C76FE77B00>, 'render_kw': {'class': 'form-control'}, 'default': 'ppp'})> <class 'wtforms.fields.core.UnboundField'> ”“”
class UnboundField(object):
_formfield = True
creation_counter = 0 # 初始值为0 def __init__(self, field_class, *args, **kwargs):
UnboundField.creation_counter += 1 # 每事实例化一次,类变量creation_counter +1
self.field_class = field_class
self.args = args
self.kwargs = kwargs
self.creation_counter = UnboundField.creation_counter # 实例自己的creation_counter = 被实例化先后的序号, def bind(self, form, name, prefix='', translations=None, **kwargs):
kw = dict(
self.kwargs,
_form=form,
_prefix=prefix,
_name=name,
_translations=translations,
**kwargs
)
return self.field_class(*self.args, **kw) def __repr__(self):
return '<UnboundField(%s, %r, %r)>' % (self.field_class.__name__, self.args, self.kwargs)
UnboundField源码
所以可知
RegisterForm._unbound_fields = None
RegisterForm._wtforms_meta = None
RegisterForm.name = UnboundField(creation_counter=1,simple.StringField)
RegisterForm.pwd = UnboundField(creation_counter=,2,simple.PasswordField)
2.接着在视图函数中RegisterForm中实例化发生了什么?
补充:dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。
class A:
x = 'xxx'
age = 111
def __init__(self,name):
self.name = 'cp' def say_hi(self):
print('hi') print(dir(A))
a = A('cp')
print("="*40)
print(dir(a))
"""
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', \
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', \
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'say_hi', 'x'
]
========================================
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', \
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', \
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'say_hi', 'x'
]
"""
dir示例
执行 form = RegisterForm(data={'gender': 1})
# 因为RegisterForm指定了FormMeta为元类,则()触发其__call__方法
def __call__(cls, *args, **kwargs):
if cls._unbound_fields is None:
fields = []
for name in dir(cls): # 遍历RegisterForm的成员 name为str
if not name.startswith('_'): # 可知自定义表单类中字段名不能以'_'开头
unbound_field = getattr(cls, name)
if hasattr(unbound_field, '_formfield'): # _formfield为UnboundField的类变量 默认True 如果有x=123 则为False
fields.append((name, unbound_field))
# fields = [
# (name, UnboundField(creation_counter=1,simple.StringField))
# (pwd, UnboundField(creation_counter=2,simple.PasswordField))
# ]
# 利用creation_counter序列编号按前后代码中的字段顺序排序
fields.sort(key=lambda x: (x[1].creation_counter, x[0]))
cls._unbound_fields = fields # 此时_unbound_fields的初始值None被覆盖了
if cls._wtforms_meta is None:
bases = []
for mro_class in cls.__mro__: # 从RegisterForm的mro列表中遍历
if 'Meta' in mro_class.__dict__: # 查看是否有写去过Meta类
bases.append(mro_class.Meta) # 只在Form中找到 Meta = DefaultMeta
# 相当于 type('Meta', tuple(DefaultMeta), {})
cls._wtforms_meta = type('Meta', tuple(bases), {})
return type.__call__(cls, *args, **kwargs)
接着执行: type.__call__(cls, *args, **kwargs)
因为RegisterForm及其父类没有写__new__方法,则调用object的__new__创建对象,__init__则查找到父类Form中的。
class Form(with_metaclass(FormMeta, BaseForm)):
def __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): # 上一步给self._fields赋好值后,循环
setattr(self, name, field) # 给self.name = simple.StringField() self.pwd = simple.PasswordField()
# 就可以 form.name form.pwd 了
self.process(formdata, obj, data=data, **kwargs) # BaseForm的方法 class BaseForm(object):
def __init__(self, fields, prefix='', meta=DefaultMeta()):
if prefix and prefix[-1] not in '-_;:/.':
prefix += '-' self.meta = meta
self._prefix = prefix
self._errors = None
self._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)) # fields = _unbound_fields = [
# (name, UnboundField(creation_counter=1,simple.StringField))
# (pwd, UnboundField(creation_counter=2,simple.PasswordField))
# ]
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) # 实例化simple.StringField
self._fields[name] = field # OrderedDict()向有序字典中添加排好序的fields # self._fields = OrderedDict = {
# name: simple.StringField(),
# pwd: simple.PasswordField(),
# } def process(self, formdata=None, obj=None, data=None, **kwargs):
"""
Take form, object data, and keyword arg input and have the fields
process them.
。。。
"""
formdata = self.meta.wrap_formdata(self, formdata) if data is not None:
# XXX we want to eventually process 'data' as a new entity.
# Temporarily, this can simply be merged with kwargs.
kwargs = dict(data, **kwargs) 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)
type.__call__执行
从源码中可知使用注意:
1、字段名是区分大小写的
2、字段名不能以'_'开头
3、字段名不能以'validate'开头
参考:
1.https://www.cnblogs.com/Chuck-Y/p/8260156.html?utm_source=tuicool&utm_medium=referral
2.https://www.cnblogs.com/wupeiqi/articles/8202357.html
wtforms组件使用实例及源码解析的更多相关文章
- abp vnext2.0核心组件之.Net Core默认DI组件切换到AutoFac源码解析
老版Abp对Castle的严重依赖在vnext中已经得到了解决,vnext中DI容器可以任意更换,为了实现这个功能,底层架构相较于老版abp,可以说是进行了高度重构.当然这得益于.Net Core的D ...
- Django的rest_framework认证组件之局部设置源码解析
前言: Django的rest_framework组件的功能很强大,今天来我来给大家剖析一下认证组件 下面进入正文分析,我们从视图开始,一步一步来剖析认证组件 1.进入urls文件 url(r'^lo ...
- iOS富文本组件的实现—DTCoreText源码解析 数据篇
本文转载 http://blog.cnbang.net/tech/2630/ DTCoreText是个开源的iOS富文本组件,它可以解析HTML与CSS最终用CoreText绘制出来,通常用于在一些需 ...
- iOS富文本组件的实现—DTCoreText源码解析 渲染篇
本文转载至 http://blog.cnbang.net/tech/2729/ 上一篇介绍了DTCoreText怎样把HTML+CSS解析转换成NSAttributeString,本篇接着看看怎样把N ...
- abp vnext2.0核心组件之领域实体组件源码解析
接着abp vnext2.0核心组件之模块加载组件源码解析和abp vnext2.0核心组件之.Net Core默认DI组件切换到AutoFac源码解析集合.Net Core3.1,基本环境已经完备, ...
- Flume-ng源码解析之Channel组件
如果还没看过Flume-ng源码解析之启动流程,可以点击Flume-ng源码解析之启动流程 查看 1 接口介绍 组件的分析顺序是按照上一篇中启动顺序来分析的,首先是Channel,然后是Sink,最后 ...
- Flume-ng源码解析之Sink组件
作为启动流程中第二个启动的组件,我们今天来看看Sink的细节 1 Sink Sink在agent中扮演的角色是消费者,将event输送到特定的位置 首先依然是看代码,由代码我们可以看出Sink是一个接 ...
- .Net Core缓存组件(Redis)源码解析
上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...
- admin源码解析及自定义stark组件
admin源码解析 单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单 ...
随机推荐
- java的toString方法和sort方法
public class arrayTool { public static String toString(int arr[]){ String result = ""; for ...
- Nginx location 匹配规则详解
语法规则 location [=|~|~*|^~] /uri/ { … } 模式 含义 location = /uri = 表示精确匹配,只有完全匹配上才能生效 location ^~ /uri ^~ ...
- Redis实现排行榜功能(实战)
需求前段时间,做了一个世界杯竞猜积分排行榜.对世界杯64场球赛胜负平进行猜测,猜对+1分,错误+0分,一人一场只能猜一次.1.展示前一百名列表.2.展示个人排名(如:张三,您当前的排名106579). ...
- (一)V4L2学习流程
title: V4L2学习流程 date: 2019/4/23 18:00:00 toc: true --- V4L2学习流程 参考资料 关键资料,插图让人一下子就理解了 Linux摄像头驱动1--v ...
- PLSQL Developer安装与配置
前言 PLSQL Developer软件以及需要的配置 链接:https://pan.baidu.com/s/1xHdAl1RAgtQb-oDHPah19w 密码:x41k 1 安装 解压这两个压缩包 ...
- 【转】TEA、XTEA、XXTEA加密解密算法(C语言实现)
ref : https://blog.csdn.net/gsls200808/article/details/48243019 在密码学中,微型加密算法(Tiny Encryption Algorit ...
- C++与蓝图互调
Kismet库 蓝图方法cpp使用 例:打LOG:Print String 蓝图节点的鼠标tips:Target is Kismet System Library #include "Run ...
- luogu P5320 [BJOI2019]勘破神机
传送门 首先我们要知道要求什么.显然每次放方块要放一大段不能从中间分开的部分.设\(m=2\)方案为\(f\),\(m=3\)方案为\(g\),\(m=2\)可以放一个竖的,或者两个横的,所以\(f_ ...
- 浅入深出Vue系列
浅入深出Vue导航 导航帖,直接点击标题即可. 文中所有涉及到的资源链接均在最下方列举出来了. 前言 基础篇 浅入深出Vue:工具准备之WebStorm搭建及配置 浅入深出Vue之工具准备(二):Po ...
- docker使用方式
docker使用方式安装:1.安装依赖 yum install -y yum-utils \ device-mapper-persistent-data \ lvm2 2添加yum源 yum-conf ...