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组件使用实例及源码解析的更多相关文章

  1. abp vnext2.0核心组件之.Net Core默认DI组件切换到AutoFac源码解析

    老版Abp对Castle的严重依赖在vnext中已经得到了解决,vnext中DI容器可以任意更换,为了实现这个功能,底层架构相较于老版abp,可以说是进行了高度重构.当然这得益于.Net Core的D ...

  2. Django的rest_framework认证组件之局部设置源码解析

    前言: Django的rest_framework组件的功能很强大,今天来我来给大家剖析一下认证组件 下面进入正文分析,我们从视图开始,一步一步来剖析认证组件 1.进入urls文件 url(r'^lo ...

  3. iOS富文本组件的实现—DTCoreText源码解析 数据篇

    本文转载 http://blog.cnbang.net/tech/2630/ DTCoreText是个开源的iOS富文本组件,它可以解析HTML与CSS最终用CoreText绘制出来,通常用于在一些需 ...

  4. iOS富文本组件的实现—DTCoreText源码解析 渲染篇

    本文转载至 http://blog.cnbang.net/tech/2729/ 上一篇介绍了DTCoreText怎样把HTML+CSS解析转换成NSAttributeString,本篇接着看看怎样把N ...

  5. abp vnext2.0核心组件之领域实体组件源码解析

    接着abp vnext2.0核心组件之模块加载组件源码解析和abp vnext2.0核心组件之.Net Core默认DI组件切换到AutoFac源码解析集合.Net Core3.1,基本环境已经完备, ...

  6. Flume-ng源码解析之Channel组件

    如果还没看过Flume-ng源码解析之启动流程,可以点击Flume-ng源码解析之启动流程 查看 1 接口介绍 组件的分析顺序是按照上一篇中启动顺序来分析的,首先是Channel,然后是Sink,最后 ...

  7. Flume-ng源码解析之Sink组件

    作为启动流程中第二个启动的组件,我们今天来看看Sink的细节 1 Sink Sink在agent中扮演的角色是消费者,将event输送到特定的位置 首先依然是看代码,由代码我们可以看出Sink是一个接 ...

  8. .Net Core缓存组件(Redis)源码解析

    上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...

  9. admin源码解析及自定义stark组件

    admin源码解析 单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单 ...

随机推荐

  1. calico客户端工具calicoctl

    工具介绍: Calico的二进制程序文件calicoctl可以直接操作Calico存储来查看,修改或配置Calico系统特性. 三种方式部署calicoctl:①二进制安装:②docker安装:③运行 ...

  2. VBScript 学习笔记

    创建一个变量 VBScript 变量名称的规则: 必须以字母开头 不能包含点号(.) 不能超过 255 个字符 在 VBScript 的缩写中,所有的变量都与类型 variant 相关,可存储不同类型 ...

  3. python第十四天

    今日内容 1. 带参装饰器  |  wrapper 2. 迭代器 3. 可迭代对象 4.迭代器对象 5.for 迭代器 6.枚举对象 带参装饰器 是指装饰器为被装饰的函数添加新功能,需要外界的参数 - ...

  4. MySql实现远程访问配置

    1.新建用户远程连接mysql数据库grant all on *.* to admin@'%' identified by '123456' with grant option; flush priv ...

  5. (二叉树 递归) leetcode 889. Construct Binary Tree from Preorder and Postorder Traversal

    Return any binary tree that matches the given preorder and postorder traversals. Values in the trave ...

  6. Windows下U盘管理程序

    一个操作系统的作业,生成的程序需要使用管理员权限运行,参考了很多网上的代码,如果打开错误,请修改字符集为使用多字节字符集,并且调整为release模式. 作业的内容如下: 任务操作系统API应用体验与 ...

  7. 基于jeesite的cms系统(二):整体设计

    一.菜单设计 在系统管理-菜单管理中可以设置内容管理菜单(自动生成) 注意:归属模块应属于核心模块core.如果新加的的菜单设置为内容管理模块cms,系统下次重启时会重置本次设置,具体原因不详. 二. ...

  8. Modbus通讯两种传输方式

    控制器能设置为两种传输模式(ASCII或RTU)中的任何一种在标准的Modbus网络通信.用户选择想要的模式,包括串口通信参数(波特率.校验方式等),在配置每个控制器的时候,在一个Modbus网络上的 ...

  9. full join no满连接的使用

    查询各个部门工资范围,按照1000~2000,2000~3000....这样的格式显示人数 select * from (select job,count(*) as "1000~2000& ...

  10. 模块--random

    random模块 1       random.random() print(random.random()) 0-1 之间随机小数 不包含1 2       random.uniform(a,b) ...