• Flask组件之wtforms


    1、简介

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

      安装:

    pip3 install wtforms
    

    2、用户登录注册示例

    2.1、用户登录

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

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

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

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 from flask import Flask, render_template, request, redirect
     4 from wtforms import Form
     5 from wtforms.fields import core
     6 from wtforms.fields import html5
     7 from wtforms.fields import simple
     8 from wtforms import validators
     9 from wtforms import widgets
    10 
    11 app = Flask(__name__, template_folder='templates')
    12 app.debug = True
    13 
    14 
    15 class LoginForm(Form):
    16     name = simple.StringField(
    17         label='用户名',
    18         validators=[
    19             validators.DataRequired(message='用户名不能为空.'),
    20             validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
    21         ],
    22         widget=widgets.TextInput(),
    23         render_kw={'class': 'form-control'}
    24 
    25     )
    26     pwd = simple.PasswordField(
    27         label='密码',
    28         validators=[
    29             validators.DataRequired(message='密码不能为空.'),
    30             validators.Length(min=8, message='用户名长度必须大于%(min)d'),
    31             validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[$@$!%*?&])[A-Za-zd$@$!%*?&]{8,}",
    32                               message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')
    33 
    34         ],
    35         widget=widgets.PasswordInput(),
    36         render_kw={'class': 'form-control'}
    37     )
    38 
    39 
    40 
    41 @app.route('/login', methods=['GET', 'POST'])
    42 def login():
    43     if request.method == 'GET':
    44         form = LoginForm()
    45         return render_template('login.html', form=form)
    46     else:
    47         form = LoginForm(formdata=request.form)
    48         if form.validate():
    49             print('用户提交数据通过格式验证,提交的值为:', form.data)
    50         else:
    51             print(form.errors)
    52         return render_template('login.html', form=form)
    53 
    54 if __name__ == '__main__':
    55     app.run()
    app.py
     1 <!DOCTYPE html>
     2 <html lang="en">
     3 <head>
     4     <meta charset="UTF-8">
     5     <title>Title</title>
     6 </head>
     7 <body>
     8 <h1>登录</h1>
     9 <form method="post">
    10     <!--<input type="text" name="name">-->
    11     <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>
    12 
    13     <!--<input type="password" name="pwd">-->
    14     <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
    15     <input type="submit" value="提交">
    16 </form>
    17 </body>
    18 </html>
    login.html

    2.2、用户注册

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

      1 from flask import Flask, render_template, request, redirect
      2 from wtforms import Form
      3 from wtforms.fields import core
      4 from wtforms.fields import html5
      5 from wtforms.fields import simple
      6 from wtforms import validators
      7 from wtforms import widgets
      8 
      9 app = Flask(__name__, template_folder='templates')
     10 app.debug = True
     11 
     12 
     13 
     14 class RegisterForm(Form):
     15     name = simple.StringField(
     16         label='用户名',
     17         validators=[
     18             validators.DataRequired()
     19         ],
     20         widget=widgets.TextInput(),
     21         render_kw={'class': 'form-control'},
     22         default='alex'
     23     )
     24 
     25     pwd = simple.PasswordField(
     26         label='密码',
     27         validators=[
     28             validators.DataRequired(message='密码不能为空.')
     29         ],
     30         widget=widgets.PasswordInput(),
     31         render_kw={'class': 'form-control'}
     32     )
     33 
     34     pwd_confirm = simple.PasswordField(
     35         label='重复密码',
     36         validators=[
     37             validators.DataRequired(message='重复密码不能为空.'),
     38             validators.EqualTo('pwd', message="两次密码输入不一致")
     39         ],
     40         widget=widgets.PasswordInput(),
     41         render_kw={'class': 'form-control'}
     42     )
     43 
     44     email = html5.EmailField(
     45         label='邮箱',
     46         validators=[
     47             validators.DataRequired(message='邮箱不能为空.'),
     48             validators.Email(message='邮箱格式错误')
     49         ],
     50         widget=widgets.TextInput(input_type='email'),
     51         render_kw={'class': 'form-control'}
     52     )
     53 
     54     gender = core.RadioField(
     55         label='性别',
     56         choices=(
     57             (1, ''),
     58             (2, ''),
     59         ),
     60         coerce=int
     61     )
     62     city = core.SelectField(
     63         label='城市',
     64         choices=(
     65             ('bj', '北京'),
     66             ('sh', '上海'),
     67         )
     68     )
     69 
     70     hobby = core.SelectMultipleField(
     71         label='爱好',
     72         choices=(
     73             (1, '篮球'),
     74             (2, '足球'),
     75         ),
     76         coerce=int
     77     )
     78 
     79     favor = core.SelectMultipleField(
     80         label='喜好',
     81         choices=(
     82             (1, '篮球'),
     83             (2, '足球'),
     84         ),
     85         widget=widgets.ListWidget(prefix_label=False),
     86         option_widget=widgets.CheckboxInput(),
     87         coerce=int,
     88         default=[1, 2]
     89     )
     90 
     91     def __init__(self, *args, **kwargs):
     92         super(RegisterForm, self).__init__(*args, **kwargs)
     93         self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))
     94 
     95     def validate_pwd_confirm(self, field):
     96         """
     97         自定义pwd_confirm字段规则,例:与pwd字段是否一致
     98         :param field: 
     99         :return: 
    100         """
    101         # 最开始初始化时,self.data中已经有所有的值
    102 
    103         if field.data != self.data['pwd']:
    104             # raise validators.ValidationError("密码不一致") # 继续后续验证
    105             raise validators.StopValidation("密码不一致")  # 不再继续后续验证
    106 
    107 
    108 @app.route('/register', methods=['GET', 'POST'])
    109 def register():
    110     if request.method == 'GET':
    111         form = RegisterForm(data={'gender': 1})
    112         return render_template('register.html', form=form)
    113     else:
    114         form = RegisterForm(formdata=request.form)
    115         if form.validate():
    116             print('用户提交数据通过格式验证,提交的值为:', form.data)
    117         else:
    118             print(form.errors)
    119         return render_template('register.html', form=form)
    120 
    121 
    122 
    123 if __name__ == '__main__':
    124     app.run()
    app.py
     1 <!DOCTYPE html>
     2 <html lang="en">
     3 <head>
     4     <meta charset="UTF-8">
     5     <title>Title</title>
     6 </head>
     7 <body>
     8 <h1>用户注册</h1>
     9 <form method="post" novalidate style="padding:0  50px">
    10     {% for item in form %}
    11     <p>{{item.label}}: {{item}} {{item.errors[0] }}</p>
    12     {% endfor %}
    13     <input type="submit" value="提交">
    14 </form>
    15 </body>
    16 </html>
    register.html

    2.3、meta

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 from flask import Flask, render_template, request, redirect, session
     4 from wtforms import Form
     5 from wtforms.csrf.core import CSRF
     6 from wtforms.fields import core
     7 from wtforms.fields import html5
     8 from wtforms.fields import simple
     9 from wtforms import validators
    10 from wtforms import widgets
    11 from hashlib import md5
    12 
    13 app = Flask(__name__, template_folder='templates')
    14 app.debug = True
    15 
    16 
    17 class MyCSRF(CSRF):
    18     """
    19     Generate a CSRF token based on the user's IP. I am probably not very
    20     secure, so don't use me.
    21     """
    22 
    23     def setup_form(self, form):
    24         self.csrf_context = form.meta.csrf_context()
    25         self.csrf_secret = form.meta.csrf_secret
    26         return super(MyCSRF, self).setup_form(form)
    27 
    28     def generate_csrf_token(self, csrf_token):
    29         gid = self.csrf_secret + self.csrf_context
    30         token = md5(gid.encode('utf-8')).hexdigest()
    31         return token
    32 
    33     def validate_csrf_token(self, form, field):
    34         print(field.data, field.current_token)
    35         if field.data != field.current_token:
    36             raise ValueError('Invalid CSRF')
    37 
    38 
    39 class TestForm(Form):
    40     name = html5.EmailField(label='用户名')
    41     pwd = simple.StringField(label='密码')
    42 
    43     class Meta:
    44         # -- CSRF
    45         # 是否自动生成CSRF标签
    46         csrf = True
    47         # 生成CSRF标签name
    48         csrf_field_name = 'csrf_token'
    49 
    50         # 自动生成标签的值,加密用的csrf_secret
    51         csrf_secret = 'xxxxxx'
    52         # 自动生成标签的值,加密用的csrf_context
    53         csrf_context = lambda x: request.url
    54         # 生成和比较csrf标签
    55         csrf_class = MyCSRF
    56 
    57         # -- i18n
    58         # 是否支持本地化
    59         # locales = False
    60         locales = ('zh', 'en')
    61         # 是否对本地化进行缓存
    62         cache_translations = True
    63         # 保存本地化缓存信息的字段
    64         translations_cache = {}
    65 
    66 
    67 @app.route('/index/', methods=['GET', 'POST'])
    68 def index():
    69     if request.method == 'GET':
    70         form = TestForm()
    71     else:
    72         form = TestForm(formdata=request.form)
    73         if form.validate():
    74             print(form)
    75     return render_template('index.html', form=form)
    76 
    77 
    78 if __name__ == '__main__':
    79     app.run()
    View Code

    3、其他

    3.1、metaclass

     1 class MyType(type):
     2     def __init__(self, *args, **kwargs):
     3         print('MyType创建类',self)
     4         super(MyType, self).__init__(*args, **kwargs)
     5 
     6     def __call__(self, *args, **kwargs):
     7         obj = super(MyType, self).__call__(*args, **kwargs)
     8         print('类创建对象', self, obj)
     9         return obj
    10 
    11 
    12 class Foo(object,metaclass=MyType):
    13     user = 'wupeiqi'
    14     age = 18
    15 
    16 obj = Foo()
    示例一
     1 class MyType(type):
     2     def __init__(self, *args, **kwargs):
     3         super(MyType, self).__init__(*args, **kwargs)
     4 
     5     def __call__(cls, *args, **kwargs):
     6         v = dir(cls)
     7         obj = super(MyType, cls).__call__(*args, **kwargs)
     8         return obj
     9 
    10 
    11 class Foo(MyType('MyType', (object,), {})):
    12     user = 'wupeiqi'
    13     age = 18
    14 
    15 
    16 obj = Foo()
    示例二
     1 class MyType(type):
     2     def __init__(self, *args, **kwargs):
     3         super(MyType, self).__init__(*args, **kwargs)
     4 
     5     def __call__(cls, *args, **kwargs):
     6         v = dir(cls)
     7         obj = super(MyType, cls).__call__(*args, **kwargs)
     8         return obj
     9 
    10 
    11 def with_metaclass(arg,base):
    12     return MyType('MyType', (base,), {})
    13 
    14 
    15 class Foo(with_metaclass(MyType,object)):
    16     user = 'wupeiqi'
    17     age = 18
    18 
    19 
    20 obj = Foo()
    示例三

    3.2. 实例化流程分析

     1 # 源码流程
     2     1. 执行type的 __call__ 方法,读取字段到静态字段 cls._unbound_fields 中; meta类读取到cls._wtforms_meta中
     3     2. 执行构造方法
     4         
     5         a. 循环cls._unbound_fields中的字段,并执行字段的bind方法,然后将返回值添加到 self._fields[name] 中。
     6             即:
     7                 _fields = {
     8                     name: wtforms.fields.core.StringField(),
     9                 }
    10                 
    11             PS:由于字段中的__new__方法,实例化时:name = simple.StringField(label='用户名'),创建的是UnboundField(cls, *args, **kwargs),当执行完bind之后,才变成执行 wtforms.fields.core.StringField()
    12         
    13         b. 循环_fields,为对象设置属性
    14             for name, field in iteritems(self._fields):
    15                 # Set all the fields to attributes so that they obscure the class
    16                 # attributes with the same names.
    17                 setattr(self, name, field)
    18         c. 执行process,为字段设置默认值:self.process(formdata, obj, data=data, **kwargs)
    19             优先级:obj,data,formdata;
    20             
    21             再循环执行每个字段的process方法,为每个字段设置值:
    22             for name, field, in iteritems(self._fields):
    23                 if obj is not None and hasattr(obj, name):
    24                     field.process(formdata, getattr(obj, name))
    25                 elif name in kwargs:
    26                     field.process(formdata, kwargs[name])
    27                 else:
    28                     field.process(formdata)
    29             
    30             执行每个字段的process方法,为字段的data和字段的raw_data赋值
    31             def process(self, formdata, data=unset_value):
    32                 self.process_errors = []
    33                 if data is unset_value:
    34                     try:
    35                         data = self.default()
    36                     except TypeError:
    37                         data = self.default
    38         
    39                 self.object_data = data
    40         
    41                 try:
    42                     self.process_data(data)
    43                 except ValueError as e:
    44                     self.process_errors.append(e.args[0])
    45         
    46                 if formdata:
    47                     try:
    48                         if self.name in formdata:
    49                             self.raw_data = formdata.getlist(self.name)
    50                         else:
    51                             self.raw_data = []
    52                         self.process_formdata(self.raw_data)
    53                     except ValueError as e:
    54                         self.process_errors.append(e.args[0])
    55         
    56                 try:
    57                     for filter in self.filters:
    58                         self.data = filter(self.data)
    59                 except ValueError as e:
    60                     self.process_errors.append(e.args[0])
    61                 
    62         d. 页面上执行print(form.name) 时,打印标签
    63             
    64             因为执行了:
    65                 字段的 __str__ 方法
    66                 字符的 __call__ 方法
    67                 self.meta.render_field(self, kwargs)
    68                     def render_field(self, field, render_kw):
    69                         other_kw = getattr(field, 'render_kw', None)
    70                         if other_kw is not None:
    71                             render_kw = dict(other_kw, **render_kw)
    72                         return field.widget(field, **render_kw)
    73                 执行字段的插件对象的 __call__ 方法,返回标签字符串
    View Code

    3.3.验证流程分析

     1 a. 执行form的validate方法,获取钩子方法
     2             def validate(self):
     3                 extra = {}
     4                 for name in self._fields:
     5                     inline = getattr(self.__class__, 'validate_%s' % name, None)
     6                     if inline is not None:
     7                         extra[name] = [inline]
     8         
     9                 return super(Form, self).validate(extra)
    10         b. 循环每一个字段,执行字段的 validate 方法进行校验(参数传递了钩子函数)
    11             def validate(self, extra_validators=None):
    12                 self._errors = None
    13                 success = True
    14                 for name, field in iteritems(self._fields):
    15                     if extra_validators is not None and name in extra_validators:
    16                         extra = extra_validators[name]
    17                     else:
    18                         extra = tuple()
    19                     if not field.validate(self, extra):
    20                         success = False
    21                 return success
    22         c. 每个字段进行验证时候
    23             字段的pre_validate 【预留的扩展】
    24             字段的_run_validation_chain,对正则和字段的钩子函数进行校验
    25             字段的post_validate【预留的扩展】
    View Code
  • 相关阅读:
    未让换行符弄错了数据
    REPLICATE
    内存 商业智能
    sql
    PageMethods介绍
    在ASP.NET AJAX中如何判断浏览器及计算其宽高
    用JavaScript实现网页图片等比例缩放
    js技巧收集(200多个)(转自:asp.net中文俱乐部)
    C#调用ORACLE存储过程返回结果集及函数
    Using PageMethods to access Session data
  • 原文地址:https://www.cnblogs.com/bad-robot/p/10081504.html
Copyright © 2020-2023  润新知