作用

  • 生成 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. tf训练OTSU

    训练一个简单的回归网络 基础的函数如下: # coding=utf-8 import tensorflow as tf import numpy as np np.random.seed(0) # 卷 ...

  2. oracle查看某表字段类型

    来源:https://www.cnblogs.com/ufindme/p/5033843.html 今天遇到一个问题:要求在可重复执行的SQL脚本添加一段SQL代码:修改当前的数据类型.因为SQL代码 ...

  3. 关于Cocos的内存管理机制引发一些异常的解决方案

    错误:引发了异常: 读取访问权限冲突. this 是 0xDDDDDDDD.或者hero是 0xDDDDDDDD.hero是在GameController里创建的对象 这个的意思是this所指向的内存 ...

  4. GLSL版本的区别和对比

    之前尝试将一个GLSL version 110的版本写成GLSL version 330的,在此将学习过程和收获记录下来. 参考链接 GLSL Versions 介绍 你可以使用#version命令作 ...

  5. Java Observer接口和Observable类实现观察者模式

    对于观察者模式,其实Java已经为我们提供了已有的接口和类.对于订阅者(Subscribe,观察者)Java为我们提供了一个接口,JDK源码如下: package java.util; public ...

  6. maven报 Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.0:compile(defalut-compile) on project 项目名称:No such compile 'javac'

    这个问题纠结了一天,在另外一个电脑是正常的,但是从服务器下载下来到另外一个电脑的时候却出现了如下图问题 看到javac大家都会想到是编译出现问题,而本地的配置如下图所示: 看着配置都是一致的,会是哪里 ...

  7. Vim替换查找

    ##一.字符的替换及撤销(Undo操作)       ###1.替换和撤销(Undo)命令       替换和Undo命令都是针对普通模式下的操作       命令 | 说明   -----|---- ...

  8. GLSL数组类型、浮点数Uniform

    // 添加shader中的uniform ss->addUniform(new osg::Uniform("test1", false)); //设置shader中的unif ...

  9. iOS - 跳转到系统设置

    一.跳转到自己应用设置(iOS8以上系统推荐使用) //跳转到自己应用干的设置配置页(如 定位.相机.相册 这些隐私配置) [[UIApplication sharedApplication] ope ...

  10. 域渗透分析神器BloodHound

    一.安装过程: 1.首先安装JDK,要求是JDK8 JDK8下载地址 windows下按照提示自动安装好久可以了 2.安装neo4j neo4j图数据库下载地址 下载好后,进入对应的目录在命令行运行如 ...