wtforms作用:

  用于对python web框架做表单验证.

前端页面上不能这么写,原因是正确的时候form.errors中最开始是是没有错误信息的,如果这么写{{form.errors.user.0}}来显示错误信息,就会报下面截图中的错误,

{{form.errors.user.0}}  需要将它修改为{{form.user.errors[0]}},form.user它的内部会自动把没有错误信息时报错的这个机制给处理掉,所以
通过form.user.errors可以取出user这个字段的错误信息

 

wmd算法 Python 实现 python wtforms_wmd算法 Python 实现

 

小项目

wmd算法 Python 实现 python wtforms_用户名_02

wmd算法 Python 实现 python wtforms_wmd算法 Python 实现_03

from flask import Blueprint,request,render_template,session,redirect
from uuid import uuid4
import pymysql
from ..utils.sql import  SQLHelper

account = Blueprint('account',__name__)


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



class LoginForm(Form):
    user = 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'}
    )

@account.route('/login',methods=['GET','POST'])
def login():
    if request.method == "GET":
        form = LoginForm()
        return render_template('login.html',form=form)
    user = request.form.get('user')
    pwd = request.form.get('pwd')

    form  = LoginForm(formdata=request.form)    #将提交上来的数据在form中进行校验
    if not form.validate():
        print(form.errors)
        return render_template('login.html', form=form)


    print('用户提交数据通过格式验证,提交的值为:',form.data)


    obj = SQLHelper.fetch_one('select id,name from user where name=%(user)s and pwd=%(pwd)s',form.data)

    if obj:
        session.permanent = True

        session['user_info'] = {'id':obj['id'],'name':obj['name']}

        return redirect('/index')
    else:
        return render_template('login.html',msg="用户名或密码错误",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          #相当于是把值拿过来做一个强转int(值),如果choices=(('1','男')),里面的1是字符串类型的,那就不需要这个强转了,否则要记得用coerce给它变成相应的类型,必须要保证数据库中的和提交上来的是一样的(包括数据类型)
    )
    city = core.SelectField(
        label='城市',
        choices=SQLHelper.fetch_all('select id,name from city',{},None)
    )

    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):
        '''
        作用就是解决下拉框选的时候,如果给表中新增数据,在项目没有重启的情况下, 前端页面不能显示新增的信息
        原因是form中的字段都是静态字段,静态字段是在第一次执行的时候只加载一次,如果下次使用,用的
        时候还是这一次的查询,就不会到数据库中再次查询,所以就造成了在对表总新增数据后,如果项目已经被加载,数据库中新增的数据就不会显示在下
        拉框中,即使页面刷新也不会显示,解决方法就是重写一个构造方法,它每一次实例化的时候都找到city这个字段,让city的choices每一次都更新一次
        '''
        super(RegisterForm, self).__init__(*args, **kwargs)     #执行父类的构造方法,如果没有下面的那一行代码,就相当于什么都没写,还是使用的父类中的构造方法
        self.city.choices =  SQLHelper.fetch_all('select id,name from city',{},None)   #  #表中每新增一条记录,就重新到数据库中查询一次,把city.choices中的值改成最新的查询结果

    def validate_name(self, field):   #name是要验证的字段
        """
        钩子函数是以validate开头的,后面的是字段名
        自定义pwd_confirm字段规则,例:与pwd字段是否一致
        :param field:
        :return:
        """
        # 最开始初始化时,self.data中是当前传过来的所有的值:name,pwd....
        print(field.data)   # #field.data其实就是当前name传过来的值
        print(self.data)    #self.data中是当前传过来的所有的值:name,pwd....

        obj = SQLHelper.fetch_one('select id from user where name=%s',[field.data,])
        if  obj:
            # 如果用户名已经存在,就要抛出异常,下面两个哪个都行,但两者有区别
            # raise validators.ValidationError("密码不一致")  # 继续后续验证
            raise validators.StopValidation("密码不一致")  # 不再继续后续验证




@account.route('/register',methods=['GET','POST'])
def register():
    if request.method == 'GET':
        form = RegisterForm()
        return render_template('register.html',form=form)
    form = RegisterForm(formdata=request.form)#将提交上来的input框中输入的值进行验证
    if form.validate():
        print(form.data)
    else:
        print(form.errors)
    return 'dalskj'

views/account.py

wmd算法 Python 实现 python wtforms_用户名_02

wmd算法 Python 实现 python wtforms_wmd算法 Python 实现_03

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>

<h1>用户注册</h1>
<form method="post" >
    {% for item in form %}
    <p>{{item.label}}: {{item}} {{item.errors[0] }}</p>
    {% endfor %}
    <input type="submit" value="提交">
</form>

</body>
</html>

register.html

wmd算法 Python 实现 python wtforms_用户名_02

wmd算法 Python 实现 python wtforms_wmd算法 Python 实现_03

from datetime import timedelta
from redis import Redis
import pymysql
import threading
from DBUtils.PooledDB import PooledDB, SharedDBConnection

class Config(object):
    DEBUG = True
    SECRET_KEY = "dsjlajfla"
    PERMANENT_SESSION_LIFETIME = timedelta(minutes=20)  #设置session超时时间是20分钟,20分钟之后就失效了
    # 如果写上下面这一句还需要在用户登录成功之后写上session.permanent=True,才能对session的二级数据进行修改
    SESSION_REFRESH_EACH_REQUEST = True  #每次请求过来都刷新session,就不会存在session['user_info']['k'] =v不能修改的问题了,如果不写这一句,需要在修改之后加上一个session['modified']= True
    SESSION_TYPE = 'redis'

    PYMYSQL_POOL = PooledDB(
        creator=pymysql,  # 使用链接数据库的模块
        maxconnections=6,  # 连接池允许的最大连接数,0和None表示不限制连接数
        mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
        maxcached=5,  # 链接池中最多闲置的链接,0和None不限制
        maxshared=3,  # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
        blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
        maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
        setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
        ping=0,
        # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123456',
        database='user_info',
        charset='utf8'
    )



# 正式环境连接redis
class ProductionConfig(Config):
    SESSION_REDIS = Redis(host='192.168.0.11',port='6379')


#开发环境
class DevelopmentConfig(Config):
    SESSION_REDIS = Redis(host='127.0.0.1',port='6379')



class TestingConfig(Config):
    pass

settings

wmd算法 Python 实现 python wtforms_用户名_02

wmd算法 Python 实现 python wtforms_wmd算法 Python 实现_03

from  flask_pro_pymysql import create_app

app = create_app()

if __name__ == '__main__':
    app.run()

manage.py

源码流程:  以下面一段代码示例

from flask import Flask,render_template,request,redirect
from wtforms import Form,validators,widgets
from wtforms.fields import simple,core,html5


app = Flask(__name__,template_folder='templates')
app.debug = True

class LoginForm(Form):
    xxx = 123
    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'}  #给这个input标签添加属性class以及它的属性值
    )
    pwd = simple.PasswordField(
        label = '密码',
        validators = [
            validators.DataRequired(message='密码不能为空'),
            validators.Length(min=8,message='用户名或密码错误'),
            validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}",
                message='密码至少8个字符,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()

上面代码是从上到下执行的,当执行到class LoginForm(Form)这行代码时,它会找该类中有没有metaclass,如果有就使用metaclass指定的类来创建LoginForm类,如果它的基类是通过metaclass来创建的类,那么LoginForm也是通过metaclass来创建的,如果它的内部也是会使用type来创建的类.在LoginForm的父类Form中,是通过执行了一个函数,这个函数的返回值 是FormMeta('NewBase',(BaseForm,),{}) ,FormMeta是继承的type.

Form(with_metaclass(FormMeta, BaseForm))
def with_metaclass(meta, base=object):
    return meta("NewBase", (base,), {})
with_metaclass的返回值是FormMeta('NewBase',(BaseForm,),{})
FormMeta(type)

关于metaclass的相关内容查看,

所以当代码执行到class LoginForm(Form),会先执行FormMeta类的__init__方法,它里面定义了两个变量,此时FormMeta中的cls就指的是LoginForm类

class FormMeta(type):
    def __init__(cls, name, bases, attrs):
        type.__init__(cls, name, bases, attrs)
        cls._unbound_fields = None
        cls._wtforms_meta = None

所以,此时LoginForm中就多了两个变量

LoginForm._unbound_fields = None
LoginForm._wtforms_meta = None

另外这个类中还有另外一个变量
LoginForm.xxx = 123
LoginForm.name=UnboundField(simple.StringField,*args,*kwargs,creation_counter=1)
LoginForm.pwd=UnboundField(simple.PasswordField,*args,*kwargs,creation_counter=2)

在login视图中当执行到form = LoginForm()时,会执行FormMeta中的__call__方法

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'):
                        fields.append((name, unbound_field))
     
            fields.sort(key=lambda x: (x[1].creation_counter, x[0]))
            cls._unbound_fields = fields

        # Create a subclass of the 'class Meta' using all the ancestors.
        if cls._wtforms_meta is None:
            bases = []
            for mro_class in cls.__mro__:
                if 'Meta' in mro_class.__dict__:
                    bases.append(mro_class.Meta)
            cls._wtforms_meta = type('Meta', tuple(bases), {})
        return type.__call__(cls, *args, **kwargs)

开始时LoginForm._unbound_fields = None,所以会走判断,里面定义了fields=[],dir(LoginForm)=[ _unbound_fields,_wtforms_meta,xxx ,name,pwd],name就是这个dir(LoginForm)中的一个个的元素,如果name不是以_开头的,通过反射找到name和pwd这两个对象,通过hasattr后此时fields=[('name',UnboundField(simple.StringField,*args,*kwargs,creation_counter=1),('pwd',UnboundField(simple.PasswordField,*args,*kwargs,creation_counter=2)].源码中的

fields.sort(key=lambda x: (x[1].creation_counter, x[0]))  #如果没有creation_counter,按照x[0]就是按照fields中的索引进行排序;creation_counter的值和它写的顺序有关
cls._unbound_fields = fields

 

 是将fields进行排序,然后将fields赋值给LoginForm._unbound_fields.和_unbound_fields的一样,_wtforms_meta的起始值也是None,所以在上面的_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)
            cls._wtforms_meta = type('Meta', tuple(bases), {})
        return type.__call__(cls, *args, **kwargs)

 

cls.__mro__是LoginForm的继承顺序(LoginForm,Form,BaseForm,object),如果Meta在前面元祖的类属性中,就把这个类.Meta添加到Base列表中,Meta只存在于Form类中.所以Base=[DefaultMeta,]

class Form(with_metaclass(FormMeta, BaseForm)):
    
    Meta = DefaultMeta

 

此时LoginForm.wtforms_meta=type('Meta', tuple(bases), {}),如果在LoginForm中定义了Meta那就会优先找LoginForm中定义的Meta,如果找不到才会找到这里的Meta

type('Meta', tuple(DefaultMeta), {}) 就相当于是创建了一个Meta类,Meta类继承了DefaultMeta类
Meta(DefaultMeta):
pass

 

最终FormMeta的__call__方法执行完之后,此时

LoginForm._unbound_fields = [('name',UnboundField(simple.StringField,*args,*kwargs,creation_counter=1),('pwd',UnboundField(simple.PasswordField,*args,*kwargs,creation_counter=2)]
LoginForm._wtforms_meta = type('Meta', tuple(DefaultMeta), {}).

接着就会执行LoginForm中的__new__方法,它里面没有到__mro__的继承顺序中去找,我找了一下里面都没有__new__方法,那就继续执行__init__方法,找到form类中就有__init__方法
class Form(with_metaclass(FormMeta, BaseForm)):
    
    Meta = DefaultMeta

    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):
            # Set all the fields to attributes so that they obscure the class
            # attributes with the same names.
            setattr(self, name, field)
        self.process(formdata, obj, data=data, **kwargs)

 

super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)中是将LoginForm._unbound_fields传进去,super继承的是BaseForm类中的__init__方法,BaseForm.__init__中的
下面一段代码的作用就是实例化simple.StringField().此时_fields={'name':simple.StringField(),'pwd':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)    
            self._fields[name] = field
在form类中继续走for循环,for循环中将LoginForm()._firlds设置成LoginForm.name=simple.StringField(),LoginForm.pwd=simple.PasswordField().
self.process(formdata, obj, data=data, **kwargs)是给生成的input框设置默认值.StringField中的返回值的执行流程如下图

 

wtforms执行流程图

wmd算法 Python 实现 python wtforms_用户名_10

wmd算法 Python 实现 python wtforms_用户名_11

wmd算法 Python 实现 python wtforms_字段_12

wmd算法 Python 实现 python wtforms_字段_13

 验证阶段:

  用户提交的是post请求,login视图在做处理时执行到

...
form = LoginForm(formdata=request.form)
if form.validate():
  ...

 

form在对其提交的数据进行校验,也就是执行了.validate方法,它首先会在LoginForm中找validate方法,找不到回到父类中找,在Form中找到有validate方法

def validate(self):
        """
        Validates the form by calling `validate` on each field, passing any
        extra `Form.validate_<fieldname>` validators to the field validator.
        """
        extra = {}
        for name in self._fields:
            inline = getattr(self.__class__, 'validate_%s' % name, None)  #self.__class__ 获取的是当前的类,这一行代码是去当前类中找钩子函数
            if inline is not None:
                extra[name] = [inline]

        return super(Form, self).validate(extra)

 

 这个方法中的self._fields = [name,pwd],其中的self就是form对象,列表中的元素是LoginForm中定义的字段.inline是找到的钩子函数,如果找不到钩子函数就是None,如果inline不为None,就把它添加到extra这个字典中.super(Form, self).validate(extra)是执行了它父类中的validate方法,Form的父类是BaseForm.

def validate(self, extra_validators=None):  #extra_validators是传进来的钩子函数
       
        self._errors = None
        success = True
        for name, field in iteritems(self._fields):    #iteritems(self._fields)相当于是.items,  name就是每个字段的字段名name,pwd,field就是对象StringField和PasswordField
            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 = False
        return success

 

 self._errors = None 表示是没错误,如果有钩子函数,field.validate(self, extra)是对某个对象设定的钩子函数,就对哪个对象中的validate中进行校验. 假如是对StringField加了一个钩子函数,就要在StringField中执行validate中进行校验,但是StringField中没有这个方法,StringField的父类Field中有这个方法,所以就执行父类的validate方法.

if not stop_validation:
    chain = itertools.chain(self.validators, extra_validators)
    stop_validation = self._run_validation_chain(form, chain)

 

是将LoginForm中的validators的验证规则和定义的钩子函数的验证规则拼接到一起.执行顺序是先执行自己定义的验证规则,然后再执行钩子函数的验证规则.chain就是所有的验证规则,self._run_validation_chain(form,chain)中的form是
用户提交过来的数据,
def _run_validation_chain(self, form, validators):
       
        for validator in validators:
            try:
                validator(form, self)
            except StopValidation as e:
                if e.args and e.args[0]:
                    self.errors.append(e.args[0])
                return True
            except ValueError as e:
                self.errors.append(e.args[0])

        return False
如果执行validator(form, self)没有校验成功就会抛出一个异常StopValidation它返回True,那么这个for循环就会终止,也就是自己定义的这个规则中如果抛异常是StopValidation,那么后面的钩子函数中的校验规则就不会再去校验了,如果是
ValueError就会继续执行,把错误信息添加到errors中.