python之wtforms組件

做用

  • 生成 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 循環(迭代器回顧)。html

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

    對象是什麼,取決於 __new__ 方法的返回值。html5

    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 的一個對象):flask

    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 函數:緩存

    1 def with_metaclass(meta, base=object):
    2     return meta("NewBase", (base,), {})
    wtforms.compat.with_metaclass

    即它的返回值就是: FormMeta("NewBase", (BaseForm,), {}) 。接着查看 FormMeta 發現它繼承了 type : FormMeta(type) 。因此經過上面知識儲備中 metaclass 的學習咱們也知道它等價於這樣建立的一個類:session

    class NewBase(BaseForm,metaclass=FormMeta):
        pass

    而咱們使用 Form 時須要繼承 Form ,如上面登錄示例中。app

    到此咱們能夠肯定 LoginForm 的繼承關係: LoginForm(Form(NewBase(BaseForm(object),metaclass=FormMeta))) 。ide

    因此當腳本加載到 LoginForm 時,由於它間接繼承了 NewBase 類,而 NewBase 指定了 metaclass=FormMeta ,因此接着會執行 FormMeta.__init__ 的函數,查看:函數

    1 def __init__(cls, name, bases, attrs):
    2     type.__init__(cls, name, bases, attrs)
    3     cls._unbound_fields = None
    4     cls._wtforms_meta = None
    wtforms.form.FormMeta.__init__

    此時這裏的 cls 就是 NewBase 類了,接着 三、4 行給 NewBase 類新增了兩個字段: _unbound_fields、_wtforms_meta  ,因此當 LoginForm 建立完成時,除了上面本身定義的 name、pwd 字段,它還擁有了 _unbound_fields、_wtforms_meta 這兩個字段。類的建立部分到此就完成了,得出結論:只要咱們使用類繼承了 wtforms.form.Form ,那麼這個類在建立完成時就會默認擁有 _unbound_fields、_wtforms_meta 兩個字段post

  • 字段的建立

    查看上述 LoginForm 中的 name 字段,能夠看到它的值是經過 StringField 類實例化返回的對象。查看 StringField 類,會發現它的整個繼承體系中都沒有指定 metaclass ,因此實例化時真正返回的對象是由它的 __new__ ,它的 __new__ 方法繼承自 wtforms.fields.core.Field :
    1 def __new__(cls, *args, **kwargs):
    2     if '_form' in kwargs and '_name' in kwargs:
    3         return super(Field, cls).__new__(cls)
    4     else:
    5         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 類:

     1 class UnboundField(object):
     2     _formfield = True
     3     creation_counter = 0
     4 
     5     def __init__(self, field_class, *args, **kwargs):
     6         UnboundField.creation_counter += 1
     7         self.field_class = field_class
     8         self.args = args
     9         self.kwargs = kwargs
    10         self.creation_counter = UnboundField.creation_counter
    11 
    12     def bind(self, form, name, prefix='', translations=None, **kwargs):
    13         kw = dict(
    14             self.kwargs,
    15             _form=form,
    16             _prefix=prefix,
    17             _name=name,
    18             _translations=translations,
    19             **kwargs
    20         )
    21         return self.field_class(*self.args, **kw)
    22 
    23     def __repr__(self):
    24         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__ 方法:
     1 def __call__(cls, *args, **kwargs):
     2     if cls._unbound_fields is None:
     3         fields = []
     4         for name in dir(cls):
     5             if not name.startswith('_'):
     6                 unbound_field = getattr(cls, name)
     7                 if hasattr(unbound_field, '_formfield'):
     8                     fields.append((name, unbound_field))
     9         fields.sort(key=lambda x: (x[1].creation_counter, x[0]))
    10         cls._unbound_fields = fields
    11 
    12     if cls._wtforms_meta is None:
    13         bases = []
    14         for mro_class in cls.__mro__:
    15             if 'Meta' in mro_class.__dict__:
    16                 bases.append(mro_class.Meta)
    17         cls._wtforms_meta = type('Meta', tuple(bases), {})
    18     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 字段,以下:

    1 class Form(with_metaclass(FormMeta, BaseForm)):
    2     Meta = DefaultMeta
    wtforms.form.Form

    而後在 17 行經過 type 建立一個名爲 Meta 且繼承了 bases 中的類,將其賦值給 cls._wtforms_meta 。等價於:

    class Meta(DefaultMeta):
        pass

    接着就該繼續執行本身類的 __init__ 方法了:

    1 def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
    2     meta_obj = self._wtforms_meta()
    3     if meta is not None and isinstance(meta, dict):
    4         meta_obj.update_values(meta)
    5     super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
    6 
    7     for name, field in iteritems(self._fields):
    8         setattr(self, name, field)
    9     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__ 方法:

     1 def __init__(self, fields, prefix='', meta=DefaultMeta()):
     2     if prefix and prefix[-1] not in '-_;:/.':
     3         prefix += '-'
     4 
     5     self.meta = meta
     6     self._prefix = prefix
     7     self._errors = None
     8     self._fields = OrderedDict()
     9 
    10     if hasattr(fields, 'items'):
    11         fields = fields.items()
    12 
    13     translations = self._get_translations()
    14     extra_fields = []
    15     if meta.csrf:
    16         self._csrf = meta.build_csrf(self)
    17         extra_fields.extend(self._csrf.setup_form(self))
    18 
    19     for name, unbound_field in itertools.chain(fields, extra_fields):
    20         options = dict(name=name, prefix=prefix, translations=translations)
    21         field = meta.bind_field(self, unbound_field, options)
    22         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 方法:

    1 def bind_field(self, form, unbound_field, options):
    2     return unbound_field.bind(form=form, **options)
    wtforms.meta.DefaultMeta.bind_field

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

     1 def bind(self, form, name, prefix='', translations=None, **kwargs):
     2     kw = dict(
     3         self.kwargs,
     4         _form=form,
     5         _prefix=prefix,
     6         _name=name,
     7         _translations=translations,
     8         **kwargs
     9     )
    10     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 行執行完畢,接着看它的 七、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__ ,查看:

     1 def __init__(self, fields, prefix='', meta=DefaultMeta()):
     2     if prefix and prefix[-1] not in '-_;:/.':
     3         prefix += '-'
     4 
     5     self.meta = meta
     6     self._prefix = prefix
     7     self._errors = None
     8     self._fields = OrderedDict()
     9 
    10     if hasattr(fields, 'items'):
    11         fields = fields.items()
    12 
    13     translations = self._get_translations()
    14     extra_fields = []
    15     if meta.csrf:
    16         self._csrf = meta.build_csrf(self)
    17         extra_fields.extend(self._csrf.setup_form(self))
    18 
    19     for name, unbound_field in itertools.chain(fields, extra_fields):
    20         options = dict(name=name, prefix=prefix, translations=translations)
    21         field = meta.bind_field(self, unbound_field, options)
    22         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) 方法:

    1 def build_csrf(self, form):
    2     if self.csrf_class is not None:
    3         return self.csrf_class()
    4 
    5     from wtforms.csrf.session import SessionCSRF
    6     return SessionCSRF()
    wtforms.meta.DefaultMeta.build_csrf

    接着執行 二、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 方法的返回值:

     1 field_class = CSRFTokenField
     2 
     3 def setup_form(self, form):
     4     meta = form.meta
     5     field_name = meta.csrf_field_name
     6     unbound_field = self.field_class(
     7         label='CSRF Token',
     8         csrf_impl=self
     9     )
    10     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 方法觸發的,查看該方法:

    1 def validate(self):
    2     extra = {}
    3     for name in self._fields:
    4         inline = getattr(self.__class__, 'validate_%s' % name, None)
    5         if inline is not None:
    6             extra[name] = [inline]
    7 
    8     return super(Form, self).validate(extra)
    wtforms.form.Form.valdate

    接着第 8 行會繼續執行父類的 validate 方法:

     1 def validate(self, extra_validators=None):
     2     self._errors = None
     3     success = True
     4     for name, field in iteritems(self._fields):
     5         if extra_validators is not None and name in extra_validators:
     6             extra = extra_validators[name]
     7         else:
     8             extra = tuple()
     9         if not field.validate(self, extra):
    10             success = False
    11     return success
    wtforms.form.BaseForm.validate

    遍歷 self._fields ,在第 9 行經過 field.validate 方法給每一個字段進行校驗,這裏咱們只關注剛剛加進來的 csrf_token 字段,進入該 validate 方法:

     1 def validate(self, form, extra_validators=tuple()):
     2     self.errors = list(self.process_errors)
     3     stop_validation = False
     4 
     5     try:
     6         self.pre_validate(form)
     7     except StopValidation as e:
     8         if e.args and e.args[0]:
     9             self.errors.append(e.args[0])
    10         stop_validation = True
    11     except ValueError as e:
    12         self.errors.append(e.args[0])
    13 
    14     if not stop_validation:
    15         chain = itertools.chain(self.validators, extra_validators)
    16         stop_validation = self._run_validation_chain(form, chain)
    17 
    18     try:
    19         self.post_validate(form, stop_validation)
    20     except ValueError as e:
    21         self.errors.append(e.args[0])
    22 
    23     return len(self.errors) == 0
    wtforms.fields.core.Field.validate

    再看到第 6 行的 pre_validate 方法:

    1 def pre_validate(self, form):
    2     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 驗證的邏輯。

相關文章
相關標籤/搜索