• Flask-WTForms


    WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证

    wtforms
    作用: 1.生成HTML标签(执行类里面的__str__返回的是input标签的字符串)
        2.对用户请求的数据进行校验
    使用:
      -用户登录
      -用户注册
      - 数据库获取数据并且实时更新 (重写构造方法__init__) 对于Django中form组件也是一样的。

    用户登录注册示例

    1. 用户登录

    当用户登录时候,需要对用户提交的用户名和密码进行多种格式校验。如:

    用户不能为空;用户长度必须大于6;

    密码不能为空;密码长度必须大于12;密码必须包含 字母、数字、特殊字符等(自定义正则);

    app.py

    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 LoginForm(Form):
        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'}
    
        )
        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-zd$@$!%*?&]{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)
    
    if __name__ == '__main__':
        app.run()
    

    login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>登录</h1>
    <form method="post">
        <!--<input type="text" name="name">-->
        <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>
    
        <!--<input type="password" name="pwd">-->
        <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    

      

     

    2. 用户注册

    注册页面需要让用户输入:用户名、密码、密码重复、性别、爱好等。

    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 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=session.query(Favor.id,Favor.name).all()  #动态的更新数据从数据库中 SQLalchemy
                                              #DBUtils  sqlhelper.fetchall('select id,name from td1',[])      
        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()
    
    app.py
    

      

    示例下载:点击这里 

    其他:

    1. metaclass

    	-类是由什么创建的(由 type)
    				
    					创建类时 先执行 type 的__init__方法 ----> 用来创建类
    					
    					当一个类在实例化时执行type的__call__方法,
    					__call__方法的返回值就是当前类的实例化的对象;
    					在__call__方法中:	                        
    						-先调用当时实例化这个类的__new__,创建对象;将这个对象进行返回
    						-再调用类的__init__,对象的初始化。
    

      

    class MyType(type):
        def __init__(self, *args, **kwargs):
            print('MyType创建类',self)
            super(MyType, self).__init__(*args, **kwargs)
    
        def __call__(self, *args, **kwargs):
            obj = super(MyType, self).__call__(*args, **kwargs)
            print('类创建对象', self, obj)
            return obj
    
    
    class Foo(object,metaclass=MyType):
        user = 'wupeiqi'
        age = 18
    
    obj = Foo()
    

      

    所以现在的类有两种创建的方式:

    类有两种创建的方式:
    					1.class Foo(object):pass
    					2.type('Bar',(object),{})
    

    class Foo(object):pass
    f = Foo()
    这个类执行的流程:
    1.先执行type的__init__方法创建当前的类
    2.实例化当前类的时候,调用了type的__call__方法。
    3.在type的__call__方法中调用当前类的__new__返回一个对象;然后调用__init__做初始化。

    -在wtforms 的源码里面体现的:

    		-源码
    			wtform中    类的创建
    							- type.__init__
    					    对象的创建		
    					    	- type__call__
    					    		- cls.__new__
    					    		- cls.__init__
    				class Loginform(Form):
    						name = simple.StringField(
    							label='用户名',
    							validators=[
    								validators.DataRequired(message='用户名不能为空')
    							],
    							render_kw={"class":'input-material'}
    						)			    		
    				'''
    				name = simple.StringField()
    				这里simple.StringField也是个类,没有metaclass方法,所以这个类是由type下的__init__方法所创建的
    				simple.StringField() --> 先执行__new__,再执行 __init__ 
    				simple.StringField()里面没有传入参数 --->  return UnboundField(cls, *args, **kwargs)
    				name = UnboundField(creation_counter=1,simple.StringField)
    				'''				    		
    				form = Loginform()  
    				print(form.name,type(form.name))
    
    				'''
    				实例化Loginform类的时候,也是就 FormMet对象() --> 执行FormMet类的__call__方法->type.__call__(cls, *args, **kwargs)
    				--->调用Loginform的__new__ ,__init__方法
    				FormMet类的__call__方法:
    					Loginform._unbound_fields = None
    					Loginform._wtforms_meta = None          dir(Loginform)获取这个类里面的全部内容
    					Loginform.name = UnboundField(simple.StringField)
    					
    					unbound_field = getattr(cls, name) 
    					unbound_field = UnboundField(simple.StringField)
    					UnboundField这个类中有
    						 _formfield = True;
    						 UnboundField.creation_counter += 1;这个是用来帮我们计数 从而排序的
    					
    					Loginform._unbound_fields=[
    						('name', UnboundField(simple.StringField))             
    					
    					]
    					
    					这列表里面的数据是通过creation_counter进行排完顺序的。
    					都没有__new__方法就执行object的new方法返回对象
    				;
    				
    				Loginform__init__方法:
    					 super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
    					    fields=._unbound_fields
    					 -for name, unbound_field in itertools.chain(fields, extra_fields):
    						
    						_fields=OrderedDict{
    							'name': simple.StringField()
    						}
    					
    					for name, field in iteritems(self._fields):
    						setattr(self, name, field)
    						--> self.name = simple.StringField()
    				'''
    

      

    class Form(with_metaclass(FormMeta, BaseForm)): with_metaclass(FormMeta, BaseForm)--->NewBase
    
    def with_metaclass(meta, base=object): ----> class NewBase(BaseForm,metaclass=FormMeta):
    pass
    return meta("NewBase", (base,), {})
    

      

    wtform的源码流程:

    ''''
    wtform的源码流程:
    首先创建Loginform这个类
    自己没有metaclass,但是父类有metaclass(FormMeta(type))
    所有执行FormMeta下面的__init__方法创建Loginform类
    
    
    Loginform._unbound_fields = None
    Loginform._wtforms_meta = None
    
    '''
    
    
    class Loginform(Form):
    	name = simple.StringField(
    		label='用户名',
    		validators=[
    			validators.DataRequired(message='用户名不能为空')
    		],
    		render_kw={"class":'input-material'}
    	)
    	'''
    	name = simple.StringField()
    	这里simple.StringField也是个类,没有metaclass方法,所以这个类是由type下的__init__方法所创建的
    	simple.StringField() --> 先执行__new__,再执行 __init__ 
    	simple.StringField()里面没有传入参数 --->  return UnboundField(cls, *args, **kwargs)
    	name = UnboundField(creation_counter=1,simple.StringField)
    	'''
    	pwd = simple.StringField(
    		label='密码',
    		widget=widgets.PasswordInput(),
    		validators=[
    			validators.DataRequired(message='密码不能为空')
    		],
    		render_kw={"class": 'input-material'}
    	)
    
    	agree = core.StringField(
    		widget=widgets.CheckboxInput(),
    		validators=[
    			validators.DataRequired(message='必须要确定')
    		],
    		render_kw={"class": 'checkbox-template',"value":"1"}
    	)
    
    from flask import Blueprint, request, session, render_template, redirect, url_for
    from crm.utils.sqlhelper import fetchone, insert
    from crm.utils.pwdmd5 import getmd5
    from crm.utils.account_form import Loginform, Registerform
    
    ac = Blueprint("ac", __name__)
    
    
    @ac.route("/login", methods=["GET", "POST"], )
    def login():
    
    
    	if request.method == "GET":
    		form = Loginform()
    		print(form.name,type(form.name))
    
    		'''
    		实例化Loginform类的时候,也是就 FormMet对象() --> 执行FormMet类的__call__方法->type.__call__(cls, *args, **kwargs)
    		--->调用Loginform的__new__ ,__init__方法
    		FormMet类的__call__方法:
    			Loginform._unbound_fields = None
    			Loginform._wtforms_meta = None          dir(Loginform)获取这个类里面的全部内容
    			Loginform.name = UnboundField(simple.StringField)
    			
    			unbound_field = getattr(cls, name) 
    			unbound_field = UnboundField(simple.StringField)
    			UnboundField这个类中有
    				 _formfield = True;
    				 UnboundField.creation_counter += 1;这个是用来帮我们计数 从而排序的
    			
    			Loginform._unbound_fields=[
    				('name', UnboundField(simple.StringField))             
    			
    			]
    			
    			这列表里面的数据是通过creation_counter进行排完顺序的。
    			
    		都没有__new__方法就执行object的new方法返回对象;
    		
    		Loginform__init__方法:
    			 super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
    			    fields=._unbound_fields
    			 -for name, unbound_field in itertools.chain(fields, extra_fields):
    				
    				_fields=OrderedDict{
    					'name': simple.StringField()
    				}
    			
    			for name, field in iteritems(self._fields):
    				setattr(self, name, field)
    				--> self.name = simple.StringField()
    		'''
    
    
    		return render_template("login.html", error="", form=form)
    

     

    -源码补充
      - Meta (设置crsf)

      

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from flask import Flask, render_template, request, redirect, session
    from wtforms import Form
    from wtforms.csrf.core import CSRF
    from wtforms.fields import core
    from wtforms.fields import html5
    from wtforms.fields import simple
    from wtforms import validators
    from wtforms import widgets
    from hashlib import md5
    
    app = Flask(__name__, template_folder='templates')
    app.debug = True
    
    
    class MyCSRF(CSRF):
        """
        Generate a CSRF token based on the user's IP. I am probably not very
        secure, so don't use me.
        """
    
        def setup_form(self, form):
            self.csrf_context = form.meta.csrf_context()
            self.csrf_secret = form.meta.csrf_secret
            return super(MyCSRF, self).setup_form(form)
    
        def generate_csrf_token(self, csrf_token):
            gid = self.csrf_secret + self.csrf_context
            token = md5(gid.encode('utf-8')).hexdigest()
            return token
    
        def validate_csrf_token(self, form, field):
            print(field.data, field.current_token)
            if field.data != field.current_token:
                raise ValueError('Invalid CSRF')
    
    
    class TestForm(Form):
        name = html5.EmailField(label='用户名')
        pwd = simple.StringField(label='密码')
    
        class Meta:
            # -- CSRF
            # 是否自动生成CSRF标签
            csrf = True
            # 生成CSRF标签name
            csrf_field_name = 'csrf_token'
    
            # 自动生成标签的值,加密用的csrf_secret
            csrf_secret = 'xxxxxx'
            # 自动生成标签的值,加密用的csrf_context
            csrf_context = lambda x: request.url
            # 生成和比较csrf标签
            csrf_class = MyCSRF
    
            # -- i18n
            # 是否支持本地化
            # locales = False
            locales = ('zh', 'en')
            # 是否对本地化进行缓存
            cache_translations = True
            # 保存本地化缓存信息的字段
            translations_cache = {}
    
    
    @app.route('/index/', methods=['GET', 'POST'])
    def index():
        if request.method == 'GET':
            form = TestForm()
        else:
            form = TestForm(formdata=request.form)
            if form.validate():
                print(form)
        return render_template('index.html', form=form)
    
    
    if __name__ == '__main__':
        app.run()
    

      


      - 钩子函数(validate_字段名,)
        

        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("密码不一致") # 不再继续后续验证
    
     
    

      

    详细点我

      

      

  • 相关阅读:
    Docker跨平台架构的新特性buildx的启用方式
    Linux 如何安装rvm和ruby
    Linux
    ubuntu安装 vmware workstation pro 15.1.1
    docker-compose搭建golang本地开发环境
    linux 常用命令
    leetcode 1046 最后一块石头的重量
    leetcode 330 按要求补齐数组
    MySQL 字符集与比较规则
    Python 是什么语言
  • 原文地址:https://www.cnblogs.com/zenghui-python/p/11684738.html
Copyright © 2020-2023  润新知