flask wtforms組件詳解

1、簡介

  在flask內部並無提供全面的表單驗證,因此當咱們不借助第三方插件來處理時候代碼會顯得混亂,而官方推薦的一個表單驗證插件就是wtforms。wtfroms是一個支持多種web框架的form組件,主要用於對用戶請求數據的進行驗證,其的驗證流程與django中的form表單驗證由些許相似,本文將介紹wtforms組件使用方法以及驗證流程。  html

wtforms依照功能類別來講wtforms分別由如下幾個類別:
  • Forms: 主要用於表單驗證、字段定義、HTML生成,並把各類驗證流程彙集在一塊兒進行驗證。
  • Fields: 主要負責渲染(生成HTML)和數據轉換。
  • Validator:主要用於驗證用戶輸入的數據的合法性。好比Length驗證器能夠用於驗證輸入數據的長度。
  • Widgets:html插件,容許使用者在字段中經過該字典自定義html小部件。
  • Meta:用於使用者自定義wtforms功能,例如csrf功能開啓。
  • Extensions:豐富的擴展庫,能夠與其餘框架結合使用,例如django。

2、安裝使用

安裝:
pip3 install wtforms

定義Forms

簡單登錄驗證html5

app:python

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd

from  flask import Flask,render_template,request
from wtforms.fields import simple
from wtforms import Form
from wtforms import validators
from wtforms import widgets
app = Flask(__name__,template_folder="templates")

class LoginForm(Form):
    '''Form'''
    name = simple.StringField(
        label="用戶名",
        widget=widgets.TextInput(),
        validators=[
            validators.DataRequired(message="用戶名不能爲空"),
            validators.Length(max=8,min=3,message="用戶名長度必須大於%(max)d且小於%(min)d")
        ],
        render_kw={"class":"form-control"}  #設置屬性生成的html屬性
    )

    pwd = simple.PasswordField(
        label="密碼",
        validators=[
            validators.DataRequired(message="密碼不能爲空"),
            validators.Length(max=18,min=4,message="密碼長度必須大於%(max)d且小於%(min)d"),
            validators.Regexp(regex="\d+",message="密碼必須是數字"),
        ],
        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():  # 對用戶提交數據進行校驗,form.data是校驗完成後的數據字典
            print("用戶提交的數據用過格式驗證,值爲:%s"%form.data)
            return "登陸成功"
        else:
            print(form.errors,"錯誤信息")
        return render_template("login.html",form=form)


if __name__ == '__main__':
    app.run(debug=True)

login.htmlweb

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

Form類實例化參數:django

  • formdata:須要被驗證的form表單數據。
  • obj:當formdata參數爲提供時候,可使用對象,也就是會是有obj.字段的值進行驗證或設置默認值。
  • prefix: 字段前綴匹配,當傳入該參數時,全部驗證字段必須以這個開頭(無太大意義)。
  • data: 當formdata參數和obj參數都有時候,可使用該參數傳入字典格式的待驗證數據或者生成html的默認值,列如:{'usernam':'admin’}。
  • meta:用於覆蓋當前已經定義的form類的meta配置,參數格式爲字典。 

自定義驗證規則

#定義
class Myvalidators(object):
    '''自定義驗證規則'''
    def __init__(self,message):
        self.message = message
    def __call__(self, form, field):
        print(field.data,"用戶輸入的信息")
        if field.data == "admin":
            raise validators.ValidationError(self.message)


#使用
class LoginForm(Form):
    '''Form'''
    name = simple.StringField(
        label="用戶名",
        widget=widgets.TextInput(),
        validators=[ Myvalidators(message='用戶名不能是admin'),]#自定義驗證類
        render_kw={"class":"form-control"}  #設置屬性
    )

字段介紹

wtforms中的Field類主要用於數據驗證和字段渲染(生成html),如下是比較常見的字段:flask

  •  StringField    字符串字段,生成input要求字符串
  • PasswordField  密碼字段,自動將輸入轉化爲小黑點
  • DateField  日期字段,格式要求爲datetime.date同樣
  • IntergerField  整型字段,格式要求是整數
  • FloatField  文本字段,值是浮點數
  • BooleanField  複選框,值爲True或者False
  • RadioField  一組單選框
  • SelectField  下拉列表,須要注意一下的是choices參數肯定了下拉選項,可是和HTML中的<select> 標籤同樣。
  • MultipleSelectField  多選字段,可選多個值的下拉列表
  • ...

字段參數:緩存

  • label:字段別名,在頁面中能夠經過字段.label展現;
  • validators:驗證規則列表;
  • filters:過氯器列表,用於對提交數據進行過濾;
  • description:描述信息,一般用於生成幫助信息;
  • id:表示在form類定義時候字段的位置,一般你不須要定義它,默認會按照定義的前後順序排序。
  • default:默認值
  • widget:html插件,經過該插件能夠覆蓋默認的插件,更多經過用戶自定義;
  • render_kw:自定義html屬性;
  • choices:複選類型的選項 ;

示例:session

from flask import Flask,render_template,redirect,request
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

=======================simple===========================
class RegisterForm(Form):
    name = simple.StringField(
        label="用戶名",
        validators=[
            validators.DataRequired()
        ],
        widget=widgets.TextInput(),
        render_kw={"class":"form-control"},
        default="wd"
    )
    pwd = simple.PasswordField(
        label="密碼",
        validators=[
            validators.DataRequired(message="密碼不能爲空")
        ]
    )
    pwd_confim = simple.PasswordField(
        label="重複密碼",
        validators=[
            validators.DataRequired(message='重複密碼不能爲空.'),
            validators.EqualTo('pwd',message="兩次密碼不一致")
        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )

  ========================html5============================
    email = html5.EmailField(  #注意這裏用的是html5.EmailField
        label='郵箱',
        validators=[
            validators.DataRequired(message='郵箱不能爲空.'),
            validators.Email(message='郵箱格式錯誤')
        ],
        widget=widgets.TextInput(input_type='email'),
        render_kw={'class': 'form-control'}
    )

  ===================如下是用core來調用的=======================
    gender = core.RadioField(
        label="性別",
        choices=(
            (1,""),
            (1,""),
        ),
        coerce=int  #限制是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):  #這裏的self是一個RegisterForm對象
        '''重寫__init__方法'''
        super(RegisterForm,self).__init__(*args, **kwargs)  #繼承父類的init方法
        self.favor.choices =((1, '籃球'), (2, '足球'), (3, '羽毛球'))  #把RegisterForm這個類裏面的favor從新賦值,實現動態改變複選框中的選項

    def validate_pwd_confim(self,field,):
        '''
        自定義pwd_config字段規則,例:與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})  #默認是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()

register.htmlapp

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

Meta

  Meta主要用於自定義wtforms的功能,大多都是配置選項,如下是配置參數:框架

csrf = True   # 是否自動生成CSRF標籤
csrf_field_name = 'csrf_token'   # 生成CSRF標籤name
csrf_secret = 'adwadada'     # 自動生成標籤的值,加密用的csrf_secret
csrf_context = lambda x: request.url  # 自動生成標籤的值,加密用的csrf_context
csrf_class = MyCSRF         # 生成和比較csrf標籤     
locales = False      # 是否支持翻譯
locales = ('zh', 'en')  # 設置默認語言環境
cache_translations = True  # 是否對本地化進行緩存
translations_cache = {}       # 保存本地化緩存信息的字段

示例:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect, session
from wtforms import Form
from wtforms.csrf.core import CSRF
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
from hashlib import md5

app = Flask(__name__, template_folder='templates')
app.debug = True


class MyCSRF(CSRF):
    """
    Generate a CSRF token based on the user's IP. I am probably not very
    secure, so don't use me.
    """

    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)
        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('/index/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        form = TestForm()
    else:
        form = TestForm(formdata=request.form)
        if form.validate():
            print(form)
    return render_template('index.html', form=form)


if __name__ == '__main__':
    app.run()

3、實現原理

   wtforms實現原理這裏主要從三個方面進行說明:form類建立過程、實例化過程、驗證過程。從總體看其實現原理實則就是將每一個類別的功能(如Filed、validate、meta等)經過form進行組織、封裝,在form類中調用每一個類別對象的方法實現數據的驗證和html的渲染。這裏先總結下驗證流程:

  1. for循環每一個字段;
  2. 執行該字段的pre_validate鉤子函數;
  3. 執行該字段參數的validators中的驗證方法和validate_字段名鉤子函數(若是有);
  4. 執行該字段的post_validate鉤子函數;
  5. 完成當前字段的驗證,循環下一個字段,接着走該字段的二、三、4流程,直到全部字段驗證完成;

Form類建立過程

以示例中的RegisterForm爲例子,它繼承了Form:

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

    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):
            # Set all the fields to attributes so that they obscure the class
            # attributes with the same names.
            setattr(self, name, field)
        self.process(formdata, obj, data=data, **kwargs)

    def __setitem__(self, name, value):
        raise TypeError('Fields may not be added to Form instances, only classes.')

    def __delitem__(self, name):
        del self._fields[name]
        setattr(self, name, None)

    def __delattr__(self, name):
        if name in self._fields:
            self.__delitem__(name)
        else:
            # This is done for idempotency, if we have a name which is a field,
            # we want to mask it by setting the value to None.
            unbound_field = getattr(self.__class__, name, None)
            if unbound_field is not None and hasattr(unbound_field, '_formfield'):
                setattr(self, name, None)
            else:
                super(Form, self).__delattr__(name)

    def validate(self):
        """
        Validates the form by calling `validate` on each field, passing any
        extra `Form.validate_<fieldname>` validators to the field validator.
        """
        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)

其中with_metaclass(FormMeta, BaseForm):

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

這幾段代碼就等價於:

class Newbase(BaseForm,metaclass=FormMeta):
    pass

class Form(Newbase):
    pass

也就是說RegisterForm繼承Form—》Form繼承Newbase—》Newbase繼承BaseForm,所以當解釋器解釋道class RegisterForm會執行FormMeta的__init__方法用於生成RegisterForm類:

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

由其__init__方法能夠知道生成的RegisterForm中含有字段_unbound_fields和_wtforms_meta而且也包含了咱們本身定義的驗證字段(name、pwd...),而且這些字段保存了每一個Field實例化的對象,如下拿name說明:

name = simple.StringField(
        label="用戶名",
        validators=[
            validators.DataRequired()
        ],
        widget=widgets.TextInput(),
        render_kw={"class":"form-control"},
        default="wd"
    )

實例化StringField會先執行其__new__方法在執行__init__方法,而StringField繼承了Field:

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):

也就是這裏會執行Field的__new__方法,在這裏的__new__方法中,判斷_form和_name是否在參數中,剛開始kwargs裏面是label、validators這些參數,因此這裏返回UnboundField(cls, *args, **kwargs),也就是這裏的RegisterForm.name=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

實例化該對象時候,會對每一個對象實例化的時候計數,第一個對象是1,下一個+1,並保存在每一個對象的creation_counter中。最後的RegisterForm中就保存了{’name’:UnboundField(1,simple.StringField,參數),’pwd’:UnboundField(2,simple.StringField,參數)…}。

Form類實例化過程

一樣在RegisterForm實例化時候先執行__new__方法在執行__init__方法,這裏父類中沒也重寫__new__也就是看__init__方法:

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

    def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
        meta_obj = self._wtforms_meta()  # 實例化meta
        if meta is not None and isinstance(meta, dict): # 判斷meta是否存在且爲字典
            meta_obj.update_values(meta) # 覆蓋原meta的配置
            # 執行父類的構造方法
        super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)

        for name, field in iteritems(self._fields):
            # Set all the fields to attributes so that they obscure the class
            # attributes with the same names.
            setattr(self, name, field)
        self.process(formdata, obj, data=data, **kwargs)

構造方法中先實例化默認的meta,在判斷是否傳遞類meta參數,傳遞則更新原meta的配置,接着執行父類的構造方法,父類是BaseForm:

class BaseForm(object):
    """
    Base Form Class.  Provides core behaviour like field construction,
    validation, and data and error proxying.
    """

    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:  #判斷csrf配置是否爲true,用於生成csrf的input框
            self._csrf = meta.build_csrf(self)
            extra_fields.extend(self._csrf.setup_form(self))
        #循環RegisterForm中的字段,並對每一個字段進行實例化
        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

在這裏的for循環中執行meta.bind_field方法對每一個字段進行實例化,並以k,v的形式放入了self._fields屬性中。而且實例化傳遞來參數_form和_name,也就是在執行BaseForm時候判斷的兩個屬性,這裏傳遞了就走正常的實例化過程。

def bind_field(self, form, unbound_field, options):
    """
    bind_field allows potential customization of how fields are bound.

    The default implementation simply passes the options to
    :meth:`UnboundField.bind`.

    :param form: The form.
    :param unbound_field: The unbound field.
    :param options:
        A dictionary of options which are typically passed to the field.

    :return: A bound field
    """
    return unbound_field.bind(form=form, **options)



def bind(self, form, name, prefix='', translations=None, **kwargs):
    kw = dict(
        self.kwargs,
        _form=form,  #傳遞_form 
        _prefix=prefix,
        _name=name, # 傳遞_name
        _translations=translations,
        **kwargs
    )
    return self.field_class(*self.args, **kw)

繼續看Form類中的__init__方法,接着循環:

for name, field in iteritems(self._fields):
            # Set all the fields to attributes so that they obscure the class
            # attributes with the same names.
            setattr(self, name, field)
self.process(formdata, obj, data=data, **kwargs)

此時的self._fields已經包含了每一個實例化字段的對象,調用setattr爲對象設置屬性,爲了方便獲取字段,例如沒有該語句獲取字段時候經過RegisterForm()._fields[’name’],有了它直接經過RegisterForm().name獲取,繼續執行self.process(formdata, obj, data=data, **kwargs)方法,改方法用於驗證的過程,由於此時的formdata、obj都是None,因此執行了該方法無影響。

 

驗證流程

  當form對用戶提交的數據驗證時候,一樣以上述註冊爲例子,此次請求是post,一樣會走form = RegisterForm(formdata=request.form),可是此次不一樣的是formdata已經有值,讓咱們來看看process方法:

def process(self, formdata=None, obj=None, data=None, **kwargs):
    formdata = self.meta.wrap_formdata(self, formdata) 

    if data is not None: #判斷data參數
        # XXX we want to eventually process 'data' as a new entity.
        #     Temporarily, this can simply be merged with kwargs.
        kwargs = dict(data, **kwargs),更新kwargs參數

    for name, field, in iteritems(self._fields):#循環每一個字段
        if obj is not None and hasattr(obj, name):# 判斷是否有obj參數
            field.process(formdata, getattr(obj, name)) 
        elif name in kwargs:
            field.process(formdata, kwargs[name])
        else:
            field.process(formdata)

首先對用戶提交的數據進行清洗變成k,v格式,接着判斷data參數,若是不爲空則將其值更新到kwargs中,而後循環self._fields(也就是咱們定義的字段),並執行字段的process方法:

def process(self, formdata, data=unset_value):
    self.process_errors = []
    if data is unset_value:
        try:
            data = self.default()
        except TypeError:
            data = self.default

    self.object_data = data

    try:
        self.process_data(data)
    except ValueError as e:
        self.process_errors.append(e.args[0])

    if formdata is not None:
        if self.name in formdata:
            self.raw_data = formdata.getlist(self.name)
        else:
            self.raw_data = []

        try:
            self.process_formdata(self.raw_data)
        except ValueError as e:
            self.process_errors.append(e.args[0])

    try:
        for filter in self.filters:
            self.data = filter(self.data)
    except ValueError as e:
        self.process_errors.append(e.args[0])

def process_data(self, value):
    self.data = value

該方法做用是將用戶的提交的數據存放到data屬性中,接下來就是使用validate()方法開始驗證:

def validate(self):
    """
    Validates the form by calling `validate` on each field, passing any
    extra `Form.validate_<fieldname>` validators to the field validator.
    """
    extra = {}
    for name in self._fields: # 循環每一個field 
        #尋找當前類中以validate_’字段名匹配的方法’,例如pwd字段就尋找validate_pwd,也就是鉤子函數
        inline = getattr(self.__class__, 'validate_%s' % name, None) 
        if inline is not None:
            extra[name] = [inline] #把鉤子函數放到extra字典中
    return super(Form, self).validate(extra) #接着調用父類的validate方法

驗證時候先獲取全部每一個字段定義的validate_+'字段名'匹配的方法,並保存在extra字典中,在執行父類的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): # 執行字段的validate方法
                success = False
        return success

該方法主要用於和須要驗證的字段進行匹配,而後在執行每一個字段的validate方法:

 def validate(self, form, extra_validators=tuple()):
        self.errors = list(self.process_errors)
        stop_validation = False

        # Call pre_validate
        try:
            self.pre_validate(form)      # 先執行字段字段中的pre_validate方法,這是一個自定義鉤子函數
        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])

        # Run validators
        if not stop_validation:     
            chain = itertools.chain(self.validators, extra_validators)     # 拼接字段中的validator和validate_+'字段名'驗證
            stop_validation = self._run_validation_chain(form, chain)  # 執行每個驗證規則,self.validators先執行

        # Call post_validate
        try:
            self.post_validate(form, stop_validation)
        except ValueError as e:
            self.errors.append(e.args[0])

        return len(self.errors) == 0

在該方法中,先會執行內部預留給用戶自定義的字段的pre_validate方法,在將字段中的驗證規則(validator也就是咱們定義的validators=[validators.DataRequired()],)和鉤子函數(validate_+'字段名')拼接在一塊兒執行,注意這裏的validator先執行而字段的鉤子函數後執行,咱們來看怎麼執行的:

  def _run_validation_chain(self, form, validators):
       
        for validator in validators:  # 循環每一個驗證規則
            try:
                validator(form, self)   # 傳入提交數據並執行,若是是對象執行__call__,若是是函數直接調用
            except StopValidation as e:
                if e.args and e.args[0]:    
                    self.errors.append(e.args[0])   # 若是有錯誤,追加到總體錯誤中
                return True
            except ValueError as e:
                self.errors.append(e.args[0])

        return False            

很明顯就是循環每個驗證規則,並執行,有錯誤追加到總體錯誤中,接着咱們回到validate方法中,接着又會執行post_validate,這也是內置鉤子函數,容許用戶本身定義,最後這個字段的數據驗證完成了,而在開始的for循環,循環結束意味着整個驗證過程結束。
    def post_validate(self, form, validation_stopped):
        """
        Override if you need to run any field-level validation tasks after
        normal validation. This shouldn't be needed in most cases.

        :param form: The form the field belongs to.
        :param validation_stopped:
            `True` if any validator raised StopValidation.
        """
        pass
相關文章
相關標籤/搜索