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-z\d$@$!%*?&]{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

posted @ 2019-03-27 09:11  甜麦地  阅读(621)  评论(0编辑  收藏  举报