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. 关于【jq插件开发】

    很详细,原文链接:https://www.cnblogs.com/Wayou/p/jquery_plugin_tutorial.html#commentform和https://www.cnblogs ...

  2. 【简】题解 AWSL090429 【数塔问题】

    因为每次只ban一个点 而且不是永久性的 预处理出每个点从上往下和从下往上的最大值 每次询问直接暴力 被ban掉点那行去掉那点的最大值 也可以直接预处理出每行的最大值和次大值 还有种做法貌似可以过 预 ...

  3. JDK源码分析(9) LinkedHashMap

    概述 LinkedHashMap是一个关联数组.哈希表,它是线程不安全的,允许key为null,value为null.他继承自HashMap,实现了Map<K,V>接口.其内部还维护了一个 ...

  4. Spring Security 登录校验 源码解析

    传统情况下,在过滤器中做权限验证,Spring Secuirty也是在Filter中进行权限验证. 创建并注册过滤器 package com.awizdata.edubank.config; impo ...

  5. openssl实现自签名证书

    前言 证书的作用 加密通信数据,验证对象身份,保证数据完整性 什么是自签名证书 公认的证书往往都需要收费,如果客户端与服务端都是由我们自己来操控,那便可以使用自签名证书(说白了就是只是自己认可的证书) ...

  6. JGUI源码:prefixfree 这个库有时候会引起网页一直加载中(10)

    如题,大部分情况下正常,但是chrome频繁刷新时,会出现这个问题,控制台没有异常信息.最终放弃使用引用第三方库prefixfree.min.js

  7. Redux thunk中间件

    redux-thunk https://github.com/reduxjs/redux-thunk Why Do I Need This? Thunks are the recommended mi ...

  8. Thinkphp生成的验证码不显示——解决方法

    在调用验证码之前加上 ob_clean(); 不显示验证码的代码: public function verify(){ $verify = new \Think\Verify(); $verify-& ...

  9. git命令之git mergetool vi非正常退出.swp删除不了的问题

    1.git   pull命令产生无法merge的错误 使用了 git  mergetool命令然后...傻逼了 进入了vi操作界面,不会操作,非正常退出... 然后就产生了.swp相关文件,死活删除不 ...

  10. Redis 简介与命令操作

    redis 是 key-value 的数据,所以每个数据都是一个键值对,键的类型是字符串: 值的类型分为五种:string.hash.list.set(集合).zset(有序集合). 数据操作的全部命 ...