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. mysql varcahr转int类型

    cast(yysid as SIGNED INTEGER)

  2. ubuntu16.04安装中文输入法

    https://blog.csdn.net/u011795345/article/details/53041707

  3. 应用系统如何分析和获取SQL语句的执行代码

    大部分开发人员都有这样一个需求,在程序连接数据库执行时,有时需要获取具体的执行语句,以便进行相关分析,这次我向大家介绍一下通用权限管理系统提供的SQL语句执行跟踪记录,直接先看看代码吧:(这个功能我也 ...

  4. Day042---浮动 背景图设置 相对定位绝对定位

    1.练习浮动 2.文本属性和字体属性 文本对齐 ​ text-align left 左对齐 right 右对齐 center 中心对齐 justify 两边对齐 只适应于英文 text-indent ...

  5. 20175221曾祥杰 实验二《Java面向对象程序设计》

    实验二<Java面向对象程序设计> 实验报告封面 课程:Java程序设计 班级:1752班 姓名:曾祥杰 学号:20175221 指导教师:娄嘉鹏 实验日期:2019年4月17日 实验时间 ...

  6. Eclipse MAT 安装及使用

    Eclipse MAT官方网页:https://www.eclipse.org/mat/downloads.php 一.MAT是什么? MAT(Memory Analyzer Tool),一个基于Ec ...

  7. Tomcat系列(3)——Tomcat 组件及架构核心部分 4类主要组件(顶层,连接器,容器,嵌套)

    1.架构图 2. 定义 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta ...

  8. 2019秋招Java面经(未完待续)

    2019秋招Java面经(凭记忆回忆, 可能不准) 随着我们从大三升到大四...秋招也开始了. 秋招进行的还比较顺利, 刚开始没几天, 我的秋招就结束了. 到现在我玩了差不多十多天了, 总想着总结一下 ...

  9. 转载---JavaScript执行机制

    很好的一篇文章,原地址 JavaScript执行机制 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不 ...

  10. 《Java》第九周学习总结

    下载mysql 选择mysql的管理软件 idea可以直接连接 然后用库运行程序,但是没有截图,,因为想在navicat上试试,可惜速度太慢了 打开idea又很慢,所以明天再更新