zoukankan      html  css  js  c++  java
  • wtforms组件使用实例及源码解析

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

    WTforms作用:当网站中需要用到表单时,WTForms变得很有效。应该把表单定义为类,作为单独的一个模块。

    创建表单: 创建表单时,通常是创建一个Form的子类,表单的中的字段作为类的属性。

      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 
      7 from wtforms import validators
      8 from wtforms import widgets
      9 
     10 app = Flask(__name__, template_folder='templates')
     11 app.debug = True
     12 
     13 class MyValidator(object): # 自定义验证器
     14     def __init__(self,message):
     15         self.message = message
     16 
     17     def __call__(self, form, field):
     18         # print(field.data)
     19         if field.data == '王浩':
     20             return None
     21         raise validators.StopValidation(self.message)
     22 
     23 
     24 class LoginForm(Form):
     25     name = simple.StringField(
     26         label='用户名',
     27         validators=[
     28             # MyValidator(message='用户名必须等于王浩')
     29             validators.DataRequired(message='用户名不能为空.'),
     30             validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
     31         ],
     32         widget=widgets.TextInput(),
     33         render_kw={'class': 'form-control'}
     34     )
     35     pwd = simple.PasswordField(
     36         label='密码',
     37         validators=[
     38             validators.DataRequired(message='密码不能为空.'),
     39             validators.Length(min=8, message='用户名长度必须大于%(min)d'),
     40             validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[$@$!%*?&])[A-Za-zd$@$!%*?&]{8,}",
     41                               message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')
     42         ],
     43         widget=widgets.PasswordInput(),
     44         render_kw={'class': 'form-control'}
     45     )
     46 
     47 
     48 @app.route('/login', methods=['GET', 'POST'])
     49 def login():
     50     if request.method == 'GET':
     51         form = LoginForm()
     52         return render_template('login.html', form=form)
     53     else:
     54         form = LoginForm(formdata=request.form)
     55         if form.validate():
     56             print('用户提交数据通过格式验证,提交的值为:', form.data)
     57         else:
     58             print(form.errors)
     59         return render_template('login.html', form=form)
     60 
     61 
     62 # ########################### 用户注册 ##########################
     63 class RegisterForm(Form):
     64     name = simple.StringField(
     65         label='用户名',
     66         validators=[
     67             validators.DataRequired()
     68         ],
     69         widget=widgets.TextInput(),
     70         render_kw={'class': 'form-control'},
     71         default='alex'
     72     )
     73 
     74     pwd = simple.PasswordField(
     75         label='密码',
     76         validators=[
     77             validators.DataRequired(message='密码不能为空.')
     78         ],
     79         widget=widgets.PasswordInput(),
     80         render_kw={'class': 'form-control'}
     81     )
     82 
     83     pwd_confirm = simple.PasswordField(
     84         label='重复密码',
     85         validators=[
     86             validators.DataRequired(message='重复密码不能为空.'),
     87             validators.EqualTo('pwd', message="两次密码输入不一致")
     88         ],
     89         widget=widgets.PasswordInput(),
     90         render_kw={'class': 'form-control'}
     91     )
     92 
     93     email = html5.EmailField(
     94         label='邮箱',
     95         validators=[
     96             validators.DataRequired(message='邮箱不能为空.'),
     97             validators.Email(message='邮箱格式错误')
     98         ],
     99         widget=widgets.TextInput(input_type='email'),
    100         render_kw={'class': 'form-control'}
    101     )
    102 
    103     gender = core.RadioField(
    104         label='性别',
    105         choices=(
    106             (1, ''),
    107             (2, ''),
    108         ),
    109         coerce=int
    110     )
    111     city = core.SelectField(
    112         label='城市',
    113         choices=(
    114             ('bj', '北京'),
    115             ('sh', '上海'),
    116         )
    117     )
    118 
    119     hobby = core.SelectMultipleField(
    120         label='爱好',
    121         choices=(
    122             (1, '篮球'),
    123             (2, '足球'),
    124         ),
    125         coerce=int
    126     )
    127 
    128     favor = core.SelectMultipleField(
    129         label='喜好',
    130         choices=(
    131             (1, '篮球'),
    132             (2, '足球'),
    133         ),
    134         widget=widgets.ListWidget(prefix_label=False),
    135         option_widget=widgets.CheckboxInput(),
    136         coerce=int,
    137         default=[1, 2]
    138     )
    139 
    140     def __init__(self, *args, **kwargs):
    141         super(RegisterForm, self).__init__(*args, **kwargs)
    142         self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))
    143 
    144     def validate_pwd_confirm(self, field):
    145         """
    146         自定义pwd_confirm字段规则,例:与pwd字段是否一致
    147         :param field:
    148         :return:
    149         """
    150         # 最开始初始化时,self.data中已经有所有的值
    151 
    152         if field.data != self.data['pwd']:
    153             # raise validators.ValidationError("密码不一致") # 继续后续验证
    154             raise validators.StopValidation("密码不一致")  # 不再继续后续验证
    155 
    156 
    157 @app.route('/register', methods=['GET', 'POST'])
    158 def register():
    159     if request.method == 'GET':
    160         # 设置默认值
    161         form = RegisterForm(data={'gender': 1})
    162         return render_template('register.html', form=form)
    163     else:
    164         form = RegisterForm(formdata=request.form)
    165         if form.validate():
    166             print('用户提交数据通过格式验证,提交的值为:', form.data)
    167         else:
    168             print(form.errors)
    169         return render_template('register.html', form=form)
    170 
    171 
    172 if __name__ == '__main__':
    173     app.run()
    注册登录页面自定义表单
     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>
    注册.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__(),该方法将最大限度地收集参数信息。

     1 class A:
     2     x = 'xxx'
     3     age = 111
     4     def __init__(self,name):
     5         self.name = 'cp'
     6 
     7     def say_hi(self):
     8         print('hi')
     9 
    10 print(dir(A))
    11 a = A('cp')
    12 print("="*40)
    13 print(dir(a))
    14 """
    15 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',  
    16 '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__',  
    17 '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',  '__subclasshook__', '__weakref__', 'age', 'say_hi', 'x'
    18 ]
    19 ========================================
    20 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',  
    21  '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__',  
    22  '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'say_hi', 'x'
    23  ]
    24 """
    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中的。

     1 class Form(with_metaclass(FormMeta, BaseForm)):
     2     def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
     3         meta_obj = self._wtforms_meta()
     4         if meta is not None and isinstance(meta, dict):
     5             meta_obj.update_values(meta)
     6         super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix) # 调用父类的方法
     7 
     8 
     9         for name, field in iteritems(self._fields): # 上一步给self._fields赋好值后,循环
    10             setattr(self, name, field) # 给self.name = simple.StringField()  self.pwd = simple.PasswordField()
    11             # 就可以 form.name form.pwd 了
    12         self.process(formdata, obj, data=data, **kwargs) # BaseForm的方法
    13 
    14 
    15 class BaseForm(object):
    16     def __init__(self, fields, prefix='', meta=DefaultMeta()):
    17         if prefix and prefix[-1] not in '-_;:/.':
    18             prefix += '-'
    19 
    20         self.meta = meta
    21         self._prefix = prefix
    22         self._errors = None
    23         self._fields = OrderedDict()
    24 
    25         if hasattr(fields, 'items'):
    26             fields = fields.items()
    27 
    28         translations = self._get_translations()
    29         extra_fields = []
    30         if meta.csrf:
    31             self._csrf = meta.build_csrf(self)
    32             extra_fields.extend(self._csrf.setup_form(self))
    33 
    34         # fields = _unbound_fields = [
    35         #    (name, UnboundField(creation_counter=1,simple.StringField))
    36         #    (pwd, UnboundField(creation_counter=2,simple.PasswordField))
    37         # ]
    38         for name, unbound_field in itertools.chain(fields, extra_fields):
    39             options = dict(name=name, prefix=prefix, translations=translations)
    40             field = meta.bind_field(self, unbound_field, options) # 实例化simple.StringField
    41             self._fields[name] = field # OrderedDict()向有序字典中添加排好序的fields
    42 
    43         #  self._fields = OrderedDict = {
    44         #   name: simple.StringField(),
    45         #   pwd: simple.PasswordField(),
    46         # }
    47 
    48     def process(self, formdata=None, obj=None, data=None, **kwargs):
    49         """
    50         Take form, object data, and keyword arg input and have the fields
    51         process them.
    52         。。。
    53         """
    54         formdata = self.meta.wrap_formdata(self, formdata)
    55 
    56         if data is not None:
    57             # XXX we want to eventually process 'data' as a new entity.
    58             #     Temporarily, this can simply be merged with kwargs.
    59             kwargs = dict(data, **kwargs)
    60 
    61         for name, field, in iteritems(self._fields):
    62             if obj is not None and hasattr(obj, name):
    63                 field.process(formdata, getattr(obj, name))
    64             elif name in kwargs:
    65                 field.process(formdata, kwargs[name])
    66             else:
    67                 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

  • 相关阅读:
    Android消息机制(Handler)详述
    Android自定义组件-以饼状图并实现点击事件为例
    Markdown中tab的解析与4个空格 问题
    策略模式(Strategy)
    观察者模式(Observer)
    适配器模式(Adapter Class/Object)
    建造者模式(Builder)
    简单工厂模式、工厂方法模式、抽象工厂模式
    单例模式(Singleton)
    工具推荐:前后端一体化部署,效能提升开源“神器”
  • 原文地址:https://www.cnblogs.com/carlous/p/10598108.html
Copyright © 2011-2022 走看看