flask wtforms組件詳解


一、簡介

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

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

二、安裝使用

安裝:
pip3 install wtforms

定義Forms

簡單登陸驗證

app:

#!/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.html

<!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類實例化參數:

  • 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),以下是比較常見的字段:

  •  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:復選類型的選項 ;

示例:

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.html

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

三、實現原理

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

  1. for循環每個字段;
  2. 執行該字段的pre_validate鈎子函數;
  3. 執行該字段參數的validators中的驗證方法和validate_字段名鈎子函數(如果有);
  4. 執行該字段的post_validate鈎子函數;
  5. 完成當前字段的驗證,循環下一個字段,接着走該字段的2、3、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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM