作用

  • 生成 HTML 表单。
  • form 表单验证。

基本使用

安装

pip3 install wtforms

示例

  • 登录

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

    app.py

    <!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>

    login.html

  • 注册

    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 = ((1, '篮球'), (2, '足球'), (3, '羽毛球')) 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

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1>用户注册</h1>
    <form method="post" novalidate style="padding:0 50px">
    {% for item in form %}
    <p>{{item.label}}: {{item}} {{item.errors[0] }}</p>
    {% endfor %}
    <input type="submit" value="提交">
    </form>
    </body>
    </html>

    regist.html

原理

知识储备

  • 迭代器

    将一个对象变为可迭代对象,这个对象就可以被 for 循环(迭代器回顾)。

    class Foo(object):
    
        def __iter__(self):
    return iter(['aa', 'bb', 'cc']) for item in Foo():
    print(item) '''
    aa
    bb
    cc
    '''

    例:

  • __new__

    对象是什么,取决于 __new__ 方法的返回值。

    class Test2(object):
    pass class Test1(object):
    def __new__(cls, *args, **kwargs):
    # 此行才是真正实例化 Test1
    obj = super().__new__(cls, *args, **kwargs)
    return obj, Test2() print(Test1()) # (<__main__.Test1 object at 0x000000000220AB70>, <__main__.Test2 object at 0x000000000220ABE0>)

    例:

  • metaclass

    类的两种创建方式(类实际上也是 type 的一个对象):

    class Test1(object):
    a1 = 123 def func(self):
    return 'hello' print(Test1) # <class '__main__.Test1'>
    print(Test1().func()) # hello Test2 = type("Test1", (object,), {'a1': 123, 'func': lambda self: 'hello'})
    print(Test2) # <class '__main__.Test1'>
    print(Test2().func()) # hello

    例1:

    metaclass 的作用是指定当前类由谁创建,默认是 type :

    class my_type(type):
    def __new__(cls, *args, **kwargs):
    print(cls) # <class '__main__.my_type'>
    print('from my_type.__new__')
    return super().__new__(cls, *args, **kwargs) def __init__(self, *args, **kwargs):
    print(self) # <class '__main__.Test'>
    print('from my_type.__init__')
    super(my_type, self).__init__(*args, **kwargs) def __call__(self, *args, **kwargs):
    print(self) # <class '__main__.Test'>
    print('from my_type.__call__')
    obj = self.__new__(self, *args, **kwargs)
    print(obj) # <__main__.Test object at 0x0000000002300CC0>
    self.__init__(obj)
    return obj
    # 默认是由 type 中的__call__ 方法执行
    # return super(my_type, self).__call__(*args,**kwargs) class Test(object, metaclass=my_type):
    def __new__(cls, *args, **kwargs):
    print('from Test.__new__')
    return super(Test, cls).__new__(cls, *args, **kwargs) def __init__(self):
    print('from Test.__init__') def func(self):
    return 'hello' t_obj = Test()
    print(t_obj) # <__main__.Test object at 0x0000000002300CC0> '''
    输出结果:
    <class '__main__.my_type'>
    from my_type.__new__
    <class '__main__.Test'>
    from my_type.__init__
    <class '__main__.Test'>
    from my_type.__call__
    from Test.__new__
    <__main__.Test object at 0x0000000002310C18>
    from Test.__init__
    <__main__.Test object at 0x0000000002310C18> 结论:
    当加载创建 Test 类时:
    发现 Test 类指定了 metaclass=my_type,
    即指定了 Test 类由 my_type 类创建,
    此时就会先执行 my_type.__new__ 方法,
    再执行 my_type.__init__ 方法。
    当实例化 Test 类时:
    因为 Test 类实际上也算是 my_type 类的的一个对象,
    执行 Test() 时相当于执行一个对象,
    而执行一个对象时会执行该对象类型也就是 my_type 的 __call__ 方法,
    在上面示例中重写了 __call__ 方法,
    如果没重写,将默认执行 type.__call__ 方法,
    重写的内容和 type 默认执行内容相似:
    先调用将要实例化类的 __new__ 方法创建对象,
    然后将该对象作为 self 参数传入调用该类的 __init__ 方法执行。
    '''

    例2:

源码

  • 类的创建

    先查看一下 wtforms.form.Form 的继承结构: Form(with_metaclass(FormMeta, BaseForm)) ,可以看到, Form 继承的是一个 with_metaclass 函数的返回值,查看 with_metaclass 函数:

     def with_metaclass(meta, base=object):
    return meta("NewBase", (base,), {})

    wtforms.compat.with_metaclass

    即它的返回值就是: FormMeta("NewBase", (BaseForm,), {}) 。接着查看 FormMeta 发现它继承了 type : FormMeta(type) 。所以通过上面知识储备中 metaclass 的学习我们也知道它等价于这样创建的一个类:

    class NewBase(BaseForm,metaclass=FormMeta):
    pass

    而我们使用 Form 时需要继承 Form ,如上面登陆示例中。

    到此我们可以确定 LoginForm 的继承关系: LoginForm(Form(NewBase(BaseForm(object),metaclass=FormMeta))) 。

    所以当脚本加载到 LoginForm 时,因为它间接继承了 NewBase 类,而 NewBase 指定了 metaclass=FormMeta ,所以接着会执行 FormMeta.__init__ 的函数,查看:

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

    wtforms.form.FormMeta.__init__

    此时这里的 cls 就是 NewBase 类了,接着 3、4 行给 NewBase 类新增了两个字段: _unbound_fields、_wtforms_meta  ,所以当 LoginForm 创建完成时,除了上面自己定义的 name、pwd 字段,它还拥有了 _unbound_fields、_wtforms_meta 这两个字段。类的创建部分到此就完成了,得出结论:只要我们使用类继承了 wtforms.form.Form ,那么这个类在创建完成时就会默认拥有 _unbound_fields、_wtforms_meta 两个字段

  • 字段的创建

    查看上述 LoginForm 中的 name 字段,可以看到它的值是通过 StringField 类实例化返回的对象。查看 StringField 类,会发现它的整个继承体系中都没有指定 metaclass ,所以实例化时真正返回的对象是由它的 __new__ ,它的 __new__ 方法继承自 wtforms.fields.core.Field :

     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)

    wtforms.fields.core.Field.__new__

    我们实例化时并没有指定 kwargs ,所以会执行第 5 行。这里将要实例化的类也就是 StringField 作为构造参数传入 UnboundField 类,并将其实例对象返回。到这里可以知道,字段创建完成时,示例中 name 字段的值实际上是由 simple.StringField.__new__ 方法返回的 UnboundField 类实例。其它的字段类型其实都继承自 wtforms.fields.core.Field ,所以得出结论:只要是使用 wtforms 提供的字段类型创建的字段,在创建完成时都是 UnboundField 类的实例。

    wtforms 为什么要将所有字段类型都设为 UnboundField 呢?接着继续看一下 UnboundField 类:

     class UnboundField(object):
    _formfield = True
    creation_counter = 0 def __init__(self, field_class, *args, **kwargs):
    UnboundField.creation_counter += 1
    self.field_class = field_class
    self.args = args
    self.kwargs = kwargs
    self.creation_counter = UnboundField.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)

    wtforms.fields.core.UnboundField

    上面已经知道每个字段创建时都会返回 UnboundField 类的实例,所以会执行它的 __init__ 方法。第 6 行可以看到,它给自己的静态字段也就是第 3 行的 creation_counter = 0 做了自增 1 的操作,然后将 field_class 赋值给当前实例(如果对应示例中 name 字段的创建,这个 field_class 就是 StringField 类),并将自增 1 后的 UnboundField.creation_counter 赋值给当前实例的 creation_counter 属性。得出结论:类中的字段创建完成后,每个字段都有一个 creation_counter 属性,且它的值连续不重复, UnboundField 的作用就是让字段可按定义顺序排序。

  • 对象的创建

    依然以 LoginForm 为例,当实例化它时,因它继承的 NewBase 类关联了 metaclass=FormMeta ,所以会先执行 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 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)

    wtforms.form.FormMeta.__call__

    看第 2 行的判断, cls._unbound_fields 就是在类的创建时赋的值,为 None 。接着走到第 4-10 行,遍历 cls 在这里也就是 LoginForm 中所有的字段名,过滤去除以 '_' 开头的字段名,然后取出对应名字的字段将其添加到 fields 列表中,并在第 9 行给其按字段创建时保存 creation_counter 值排序,然后将其重新赋值给 cls._unbound_fields 。结论:我们自定义字段时字段名不能以 '_' 开头,否则会被过滤导致失效。

    再看 12-17 行,循环当前类的 __mro__ 字段即 LoginForm.__mro__ ,找到包含 Meta 字段的类并将 Meta 添加到 bases 列表,检查 LoginForm.__mro__ 关联的类,发现默认情况下只有 Form 类中有 Meta 字段,如下:

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

    wtforms.form.Form

    然后在 17 行通过 type 创建一个名为 Meta 且继承了 bases 中的类,将其赋值给 cls._wtforms_meta 。等价于:

    class Meta(DefaultMeta):
    pass

    接着就该继续执行自己类的 __init__ 方法了:

     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):
    setattr(self, name, field)
    self.process(formdata, obj, data=data, **kwargs)

    wtforms.form.Form.__init__

    直接看到第 5 行,将 self._unbound_fields 作为第一个参数、第 2 行的 self._wtforms_meta() (也就是上面 Meta 类实例)作为 meta 参数传入执行其父类即 wtforms.form.BaseForm 的 __init__ 方法:

     def __init__(self, fields, prefix='', meta=DefaultMeta()):
    if prefix and prefix[-1] not in '-_;:/.':
    prefix += '-' self.meta = meta
    self._prefix = prefix
    self._errors = None
    self._fields = OrderedDict() if hasattr(fields, 'items'):
    fields = fields.items() translations = self._get_translations()
    extra_fields = []
    if meta.csrf:
    self._csrf = meta.build_csrf(self)
    extra_fields.extend(self._csrf.setup_form(self)) 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

    wtforms.form.BaseForm.__init__

    看 19-22 行,遍历 fields 即传入的 self._unbound_fields ,通过 meta.bind_field(self(当前LoginForm实例), unbound_field(当前遍历字段), options) 即调用传入的 Meta 类实例的 bind_field 方法:

     def bind_field(self, form, unbound_field, options):
    return unbound_field.bind(form=form, **options)

    wtforms.meta.DefaultMeta.bind_field

    而它的返回值又是 UnboundField 实例及遍历字段的 bind 方法的返回值:

     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)

    wtforms.fields.core.UnboundField.bind

    可以看到,返回的是 self.field_class 的实例, self.field_class 已经在字段创建时赋值了,如果是 LoginForm 中的 name 字段,那么它返回的就是 StringField 的实例。接着回到 wtforms.form.BaseForm.__init__ 方法中第 22 行,将该实例赋值给 self._fields ,键为字段对应名称,即如果是 name 字段,那么 self._fields['name'] = StringField() 。至此 wtforms.form.Form.__init__ 第 5 行执行完毕,接着看它的 7、8 行:遍历 self._fields ,并将对应字段实例赋值给当前 Form 实例的对应字段,如果是 LoginForm ,那么就是

    loginForm = LoginForm()
    loginForm.name = StringField()
    loginForm.pwd = PasswordField()

    结论:在 Form 类真正实例化后,它对应类中定义的字段才真正返回它定义时指定的类型,如:

    print(type(LoginForm.name))  # <class 'wtforms.fields.core.UnboundField'>
    print(type(LoginForm.pwd)) # <class 'wtforms.fields.core.UnboundField'>
    print(type(LoginForm().name)) # <class 'wtforms.fields.core.StringField'>
    print(type(LoginForm().pwd)) # <class 'wtforms.fields.simple.PasswordField'>

补充

csrf验证

  • 使用

    from _md5 import md5
    
    from flask import Flask, render_template, request
    from wtforms import Form
    from wtforms.csrf.core import CSRF
    from wtforms.fields import html5
    from wtforms.fields import simple app = Flask(__name__, template_folder='templates')
    app.debug = True class MyCSRF(CSRF):
    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) # ef20b7d8857d8771101822f0a4cab406 ef20b7d8857d8771101822f0a4cab406
    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('/login', methods=['GET', 'POST'])
    def index():
    if request.method == 'GET':
    form = TestForm()
    else:
    form = TestForm(formdata=request.form)
    if form.validate():
    print(form.data) # {'name': 'edad', 'pwd': 'ead', 'csrf_token': 'ef20b7d8857d8771101822f0a4cab406'}
    return render_template('login.html', form=form) if __name__ == '__main__':
    app.run()

    app.py

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Login</title>
    </head>
    <body>
    <form action="/login" method="post" novalidate>
    {{form.csrf_token}}
    <p>用户名:{{form.name}} {{form.name.errors[0]}}</p>
    <p>密码:{{form.pwd}} {{form.pwd.errors[0]}}</p>
    <input type="submit" value="提交">
    </form>
    </body>
    </html>

    login.html

  • 源码

    经过上面的源码部分,我们已经知道在 Form 对象创建期间会执行 wtforms.form.BaseForm.__init__ ,查看:

     def __init__(self, fields, prefix='', meta=DefaultMeta()):
    if prefix and prefix[-1] not in '-_;:/.':
    prefix += '-' self.meta = meta
    self._prefix = prefix
    self._errors = None
    self._fields = OrderedDict() if hasattr(fields, 'items'):
    fields = fields.items() translations = self._get_translations()
    extra_fields = []
    if meta.csrf:
    self._csrf = meta.build_csrf(self)
    extra_fields.extend(self._csrf.setup_form(self)) 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

    wtforms.form.BaseForm.__init__

    看第 15-17 行, meta 指的就是 Form 对象创建过程中使用 type 创建的一个名为 'Meta' 的类的实例,而这个类继承了 bases 列表中的类, bases 列表中的类是当前使用 Form 的 __mro__ 列表中的 Meta 字段,默认在 wtforms.form.Form 中有一个 Meta = DefaultMeta 字段。而在上面使用中,我们在使用的 Form 类中自己又定义了一个 Meta 类,所以此时由 type 创建的 Meta 类结构应该如下:

    type('Meta', tuple([TestForm.Meta,wtforms.meta.DefaultMeta]), {})
    
    class Meta(TestForm.Meta,wtforms.meta.DefaultMeta):
    pass

    即 meta 指的就是如上 Meta 的实例,我们已经在自定义的 Meta 类中指定了 csrf=True ,继续执行 16 行,查看 meta.build_csrf(self) 方法:

     def build_csrf(self, form):
    if self.csrf_class is not None:
    return self.csrf_class() from wtforms.csrf.session import SessionCSRF
    return SessionCSRF()

    wtforms.meta.DefaultMeta.build_csrf

    接着执行 2、3 行,我们也指定了 csrf_class = MyCSRF ,所以它的返回值就是 MyCSRF 类的实例。即 wtforms.form.BaseForm.__init__ 的 16 行 self._csrf 的值就是 MyCSRF 类的实例。

    继续执行 17 行,先看 self._csrf.setup_form(self) ,它执行的就是我们自己定义的 MyCSRF.setup_form 函数,在该方法中给 self.csrf_context 和 self.csrf_secret 方法赋了值,接着返回父类的 setup_form 方法的返回值:

     field_class = CSRFTokenField
    
     def setup_form(self, form):
    meta = form.meta
    field_name = meta.csrf_field_name
    unbound_field = self.field_class(
    label='CSRF Token',
    csrf_impl=self
    )
    return [(field_name, unbound_field)]

    wtforms.csrf.core.CSRF.setup_form

    可以看到返回值是列表套一个元组,元组第一个元素是 meta.csrf_field_name 即 'csrf_token' ,第二个元素是字段类 CSRFTokenField(HiddenField) 的实例,所以在 wtforms.form.BaseForm.__init__ 的 17 行 self._csrf.setup_form(self) 的返回值就是这个列表,接着将其放入列表 extra_fields 中。接着在 19-22 行和 fields 合并为一个列表,遍历,通过 meta.bind_field 方法将其实例化,这次返回的是定义时对应实例,而不是 UnboundField 实例。接着以字段名为键,对应实例为值,保存到 self._fields 中。

    csrf 验证是由 Form 实例 validate 方法触发的,查看该方法:

     def validate(self):
    extra = {}
    for name in self._fields:
    inline = getattr(self.__class__, 'validate_%s' % name, None)
    if inline is not None:
    extra[name] = [inline] return super(Form, self).validate(extra)

    wtforms.form.Form.valdate

    接着第 8 行会继续执行父类的 validate 方法:

     def validate(self, extra_validators=None):
    self._errors = None
    success = True
    for name, field in iteritems(self._fields):
    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

    wtforms.form.BaseForm.validate

    遍历 self._fields ,在第 9 行通过 field.validate 方法给每个字段进行校验,这里我们只关注刚刚加进来的 csrf_token 字段,进入该 validate 方法:

     def validate(self, form, extra_validators=tuple()):
    self.errors = list(self.process_errors)
    stop_validation = False try:
    self.pre_validate(form)
    except StopValidation as e:
    if e.args and e.args[0]:
    self.errors.append(e.args[0])
    stop_validation = True
    except ValueError as e:
    self.errors.append(e.args[0]) if not stop_validation:
    chain = itertools.chain(self.validators, extra_validators)
    stop_validation = self._run_validation_chain(form, chain) try:
    self.post_validate(form, stop_validation)
    except ValueError as e:
    self.errors.append(e.args[0]) return len(self.errors) == 0

    wtforms.fields.core.Field.validate

    再看到第 6 行的 pre_validate 方法:

     def pre_validate(self, form):
    self.csrf_impl.validate_csrf_token(form, self)

    wtforms.csrf.core.CSRFTokenField.pre_validate

    接着我们会发现,它是通过 self.csrf_impl.validate_csrf_token(form, self) 验证的,而 self.csrf_impl 正是之前执行 wtforms.csrf.core.CSRF.setup_form 时放入的我们自定义 csrf 认证类的实例即 MyCSRF 的实例,所以折行实际上是执行我们在 MyCSRF 类中自己定义的 validate_csrf_token 方法,所以我们可以在此完成 csrf 验证的逻辑。

python之wtforms组件的更多相关文章

  1. flask wtforms组件详解

    一.简介 在flask内部并没有提供全面的表单验证,所以当我们不借助第三方插件来处理时候代码会显得混乱,而官方推荐的一个表单验证插件就是wtforms.wtfroms是一个支持多种web框架的form ...

  2. Flask(5)- Flask-Session组件、WTForms组件、数据库连接池(POOL)

    一.Flask-Session 我们使用过flask内置的session,知道它是把session存放在浏览器,即客户端.今天要学习的flask-session是flask的第三方组件,看一下它和fl ...

  3. WTForms组件

    WTForms组件 WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 注意: from wtforms import Form 和 from flask_wtf ...

  4. Flask中的before_request装饰器和after_request装饰器以及WTForms组件

    一.before_request装饰器和after_request装饰器 我们现在有一个Flask程序其中有3个路由和视图函数 from flask import Flask app = Flask( ...

  5. Flask(4):wtforms组件 & 数据库连接池 DBUtils

    wtforms 组件的作用: --- 生成 HTML 标签 --- form 表单验证 示例代码: app.py from flask import Flask, render_template, r ...

  6. Python中文分词组件 jieba

    jieba "结巴"中文分词:做最好的Python中文分词组件 "Jieba" Feature 支持三种分词模式: 精确模式,试图将句子最精确地切开,适合文本分 ...

  7. python web 分页组件

    闲来无事便写了一个易使用,易移植的Python Web分页组件.使用的技术栈是Python.Django.Bootstrap. 既然是易使用.易移植的组件,首先介绍一下其在django框架中的调用方式 ...

  8. Unbuntu 18.04 LTS 环境下Python安装GDAL组件

    Unbuntu 18.04 LTS 环境下Python安装GDAL组件 // 非必要 sudo add-apt-repository ppa:ubuntugis/ppa sudo apt-get up ...

  9. Python Flask wtfroms组件

    简介 WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装: pip3 install wtforms 用户登录注册示例 1. 用户登录 当用户登录时候,需要对 ...

随机推荐

  1. 代码注释中的专有词——TODO、FIXME和XXX

    [时间:2017-09] [状态:Open] [关键词:代码注释,TODO, FIXME, XXX] 阅读开源代码时可能经常遇到TODO.FIXME.XXX的单词,通常这些都是有其特殊含义的. 中文版 ...

  2. Super expression must either be null or a function, not undefined

    按照之前买的用JavaScript开发移动应用的例子来编写的,然后报了这个错.我的头部声明是这样的 var React = require('react-native'); var { Text, V ...

  3. F3D模式规则详解

    F3D有两个版本,长期版还有短期版 长期版规则 1.购买时候分配 第一队 20% to 奖金池, 56%分给所有人, 30% 持有p3d的人第二队 35% to 奖金池, 43%分给所有人, 8% 持 ...

  4. python 搭建一个http服务的小例子

    一.创建Server 1.Dos 命令 python -m BaseHTTPServer [port] 默认端口是8000, 2.Python 脚本启动 #coding:utf-8 ''' Creat ...

  5. FlexCel 插入公式和插入新行

    //http://www.tmssoftware.biz/flexcel/doc/vcl/api/FlexCel.Core/TExcelFile/InsertAndCopyRange.html#tex ...

  6. How do I use IValidatableObject? 使用IValidatableObject添加自定义属性验证

    Here's how to accomplish what I was trying to do. Validatable class: public class ValidateMe : IVali ...

  7. 使用DDL触发器同步多个数据库结构

    使用DDL触发器同步多个数据库结构 背景:当开发组比较大时,势必会分布到不同的地理位置,若无法在同一个快速网络中工作,就会造成多个开发库并存的局面,这样就需要多个开发库结构的同步,甚至是开发测试数据的 ...

  8. Orders matters: seq2seq for set 实验

    论文提出了input的顺序影响seq2seq结果 有一些输入本身是无序的怎么去处理呢 作者提出LSTM controller的方式 替代输入的LSTM encode方式         作者实验这种方 ...

  9. link元素 rel src href属性

    The SRC and HREF attributes are used to include some external entities like an image, a CSS file, a ...

  10. ubuntu百度云下载大文件

    一.实验环境 ubuntu16.04 + 百度在线云盘 二.下载小文件步骤 小文件直接点击右侧的下载按钮即可,弹出文件保存对话框 三.大文件下载步骤 大文件使用如上方式下载时提示,请使用网盘客户端下载 ...