WTForms基礎操做,可見騷師博客html
WTForms主要是兩個功能:1.生成HTML標籤 2.對數據格式進行驗證 前端
這裏主要帶你們去看下WTForms源碼,是怎麼個實現流程?html5
首先看到下面的示例代碼,捋順一下類之間的關係python
#!/usr/bin/env python # -*- coding:utf-8 -*- 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()
form類下,封裝了兩個field對象,分別爲name,pwd,每一個field對象下面又給widget封裝 了input對象正則表達式
第一:生成 HTML標籤數據庫
在前端執行{{form.name}}和後端print(form.name)的效果是同樣的,因此它會執行對象裏的__str__方法,form.name是field對象,也就是執行field對象下的__str__方法,好比StringField,它下面是沒有這方法的,可是父類中Field是有的flask
def __str__(self): return self()
代碼執行了return self(),也就會執行field類中的__call__方法後端
def __call__(self, **kwargs): return self.meta.render_field(self, kwargs)
而在render_field方法中最後又執行了field.widget(field, **render_kw),field.widget是StringField下面封裝的widgets.TextInput對象,因此它又會去調用TextInput類下的__call__方法數據結構
在父類input類下,找到了生成html標籤的代碼app
def __call__(self, field, **kwargs): kwargs.setdefault('id', field.id) kwargs.setdefault('type', self.input_type) if 'value' not in kwargs: kwargs['value'] = field._value() if 'required' not in kwargs and 'required' in getattr(field, 'flags', []): kwargs['required'] = True return HTMLString('<input %s>' % self.html_params(name=field.name, **kwargs))
第二:對數據進行驗證
class InputText(object): def __str__(self): return '<input type="text" />' class InputEmail(object): def __str__(self): return '<input type="mail" />' class StringField(object): def __init__(self,wg,reg): self.wg = wg self.reg = reg def __str__(self): return str(self.wg) def valid(self,val): import re return re.match(self.reg,val) class LoginForm(object): xyy = StringField(wg=InputText(),reg='\d+') lw = StringField(wg=InputEmail(),reg='\w+') def __str__(self,form): self.form = "用戶發來的全部數據{xyy:'df',lw:'sdf'}" def validate(self): fields = {'xyy':self.xyy,'lw':self.lw} for name,field in fields.items(): # name是 xyy、。lw # field: StringField(wg=InputText(),reg='\d+') / StringField(wg=InputEmail(),reg='\w+') # 'df' field.valid(self.form[name]) # # wp = LoginForm() # print(wp.xyy) # print(wp.lw) wp = LoginForm(formdata=request.form) wp.validate()
從上面咱們抽離出來的簡單模型,看得出:各個類,分工仍是很明確的,input類負責生成標籤,而field類負責本身字段數據驗證,而form主要統一驗證,返回結果
上面過程只是簡單分析,詳細分析,請收看下一集
開始分析前,咱們須要瞭解幾個知識點
class Foo(object): def __init__(self): pass def __new__(cls, *args, **kwargs): """ 用於生成對象 :param args: :param kwargs: :return: """ return super(Foo,cls).__new__(cls, *args, **kwargs) # return "666" obj = Foo() print(obj)
class Foo(object): AGE = 123 def __init__(self, na): self.name = na #獲取全部成員鍵值對 print(Foo.__dict__) #獲取全部成員的鍵 print(dir(Foo)) obj = Foo('laoliu') print(obj.__dict__)
v = [11,22,334,4,2] v.sort() print(v) v1 = [ (11,'alex1'), (2,'alex2'), (2,'alex3'), (7,'alex4'), ] #以元組的第一個元素排序 v1.sort(key=lambda x:x[0]) print(v1) #以元組的第一個元素排序,若是相同,則以第二個元素排序 v1.sort(key=lambda x:(x[0],x[1])) print(v1)
class F4(object): pass class F3(F4): pass class F2_5(object): pass class F2(F2_5): pass class F1(F2,F3): pass print(F1.__mro__)
接下來就開始咱們快樂的源碼分析之旅吧,let's go
仍是看到咱們的示例代碼中,代碼從上往下解釋class LoginForm(Form),會去執行建立LoginForm類裏的__init__方法,那麼建立LoginForm的類是哪一個呢,你從父類中看到這句Form(with_metaclass(FormMeta, BaseForm)),就應該很快就能肯定是FormMeta,並且FormMeta確實繼承type類,因此建立LoginForm類時會去執行FormMeta裏的__init__方法(若是這裏還不懂,能夠去看個人另外一篇博客)
def __init__(cls, name, bases, attrs): type.__init__(cls, name, bases, attrs) cls._unbound_fields = None cls._wtforms_meta = None
在__init__方法裏,給LoginForm類封裝了兩個字段_unbound_fields,_wtforms_meta,都爲None
代碼繼續往下解釋,遇到LoginForm定義兩個靜態字段name,pwd,進行simple.StringField實例化操做,因此會去執行StringField的__init__方法,執行__init__方法前,它先執行__new__方法,StringField中沒有,父類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)
kwargs是 咱們傳入的參數,咱們並無傳入了_form和_name參數,因此它會走else分支,最終name,pwd被賦值UnboundField對象,在這個對象中,封裝了StringField類和咱們實例的參數,因此建立LoginForm類,會是這個樣子
print(LoginForm.__dict__) LoginForm ={ '__module__': '__main__', 'name': <1 UnboundField(StringField, (), {'label': '用戶名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>, 'pwd': <2 UnboundField(PasswordField, (), {'label': '密碼', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, '__doc__': None, '_unbound_fields': None, '_wtforms_meta': None }
你可能會想:怎麼不直接實例StringField,而是實例一個UnboundField對象呢?UnboundField可以幫助咱們排序,它裏面維護了一個計數器,你可能又想了 幹嗎要排序?你在前端渲染時,循環form時須要一直保持一個規定順序,而在後端,咱們在定義代碼,是從上往下定義字段的,而獲取時,使用字典方式獲取的,字典是無序的,爲了還保持咱們定義的順序,就用上面對象來作到這點
到視圖函數中,實例LoginForm(),會執行下面幾個步驟
首先看到__call__方法中的這段
if cls._unbound_fields is None: #此時還爲None,會走這個分支 fields = [] for name in dir(cls): #循環 類的成員全部鍵 if not name.startswith('_'): #排除帶_成員,只剩name,pwd unbound_field = getattr(cls, name) #獲取name和pwd對應的UnboundField對象 if hasattr(unbound_field, '_formfield'): #UnboundField類中_formfield默認爲True fields.append((name, unbound_field)) #進行計算器排序 fields.sort(key=lambda x: (x[1].creation_counter, x[0])) cls._unbound_fields = fields #最終賦給LoginForm的_unbound_fields 字段
因此,此時_unbound_fields的存儲結構爲:
[ (name, UnboundField對象(計算器,StringField類,參數)), (pwd, UnboundField對象(計算器,PasswordField類,參數)) ]
咱們在看到__call__方法裏的這段代碼
if cls._wtforms_meta is None: #此時也是爲None bases = [] for mro_class in cls.__mro__: #獲取當前類的全部繼承類
#咱們寫類裏是沒有Meta這個字段的,可是繼承類Form中Meta = DefaultMeta if 'Meta' in mro_class.__dict__: bases.append(mro_class.Meta) #bases = [DefaultMeta]
#實例Meta類,繼承DefaultMeta,並賦給LoginForm的_wtforms_meta cls._wtforms_meta = type('Meta', tuple(bases), {})
執行完__call__方法後,此時LoginForm長這樣
class Meta(DefaultMeta,): pass LoginForm ={ '__module__': '__main__', 'name': <2 UnboundField(StringField, (), {'label': '用戶名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>, 'pwd': <1 UnboundField(PasswordField, (), {'label': '密碼', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, '__doc__': None, '_unbound_fields': [ (name, UnboundField對象(1,simple.StringField,參數)), (pwd, UnboundField對象(2,simple.PasswordField,參數)), ], '_wtforms_meta': Meta }
因爲LoginForm和父類中沒有__new__方法,沒有就不執行
LoginForm中沒有定義__init__方法,找到父類中Form的__init__方法執行
看到這段代碼,最後執行了又執行了父類BaseForm的__init__方法,並把_unbound_fields和meta_obj傳了進入
#實例Meta對象(主要用於生成csrf隱藏標籤) meta_obj = self._wtforms_meta() if meta is not None and isinstance(meta, dict): meta_obj.update_values(meta) #若是form裏有設置meta,更新到meta_obj #執行父類中的__init__方法 #_unbound_fields = [ # (name, UnboundField對象(1,simple.StringField,參數)), # (pwd, UnboundField對象(2,simple.PasswordField,參數)), # ], super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
BaseForm的__init__方法這段代碼,真正實例StringField對象和PasswordField是在這下面乾的
if meta.csrf: #若是有設置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): #name --> name,pwd #unbound_field --> UnboundField對象(1,simple.StringField,參數) # UnboundField對象(2,simple.PasswordField,參數) options = dict(name=name, prefix=prefix, translations=translations) field = meta.bind_field(self, unbound_field, options) #實例化Field對象後,把LoginForm對象中_fields下每一個字段的UnboundField對象替換成Field對象 self._fields[name] = field
看到meta.bind_field,最終調用 了unbound_field.bind方法
def bind(self, form, name, prefix='', translations=None, **kwargs): kw = dict( self.kwargs, _form=form, _prefix=prefix, _name=name, _translations=translations, **kwargs ) #實例化Field對象,此時參數攜帶_form和_name,在__new__方法中直接實例Field對象,再也不是UnboundField對象 return self.field_class(*self.args, **kw)
執行BaseForm的__init__方法後,此時LoginForm對象的存儲內容長這樣
form = { _fields: { name: StringField對象(), pwd: PasswordField對象(), } }
再看到Form.__init__下的另一段代碼
for name, field in iteritems(self._fields): setattr(self, name, field) #把每一個字段設置到對象中,方便支持obj.name,obj.pwd訪問 #爲每一個字段標籤設置默認值和驗證的值 self.process(formdata, obj, data=data, **kwargs)
因此此時的數據結構爲
form = { _fields: { name: StringField對象(), pwd: PasswordField對象(), } name: StringField對象(widget=widgets.TextInput()), pwd: PasswordField對象(widget=widgets.PasswordInput()) }
上面源碼分析也只在get請求下,LoginForm不傳值
那若是走post,也是LoginForm傳值,它又會怎麼走呢?
請求來的時候,它仍是要先走實例LoginForm類的流程,這和get請求裏的過程是同樣的,惟一不一樣的,在執行LoginForm父類__init__方法時,self.process(formdata, obj, data=data, **kwargs)這裏的formdata是有值的,而後看到process方法裏的這段代碼
#循環全部的字段 ''' _fields: { name: StringField對象(), pwd: PasswordField對象(), } ''' #formdata 前端傳來的值 相似於{'name':alex,'pwd':123} for name, field, in iteritems(self._fields): if obj is not None and hasattr(obj, name): field.process(formdata, getattr(obj, name)) elif name in kwargs: field.process(formdata, kwargs[name]) else: #kwargs 和 obj此時都沒值,因此會走這個分支 #field=StringField... #把前端的值 封裝在StringField對象裏,此時對象裏有 要驗證的值 和 正則 field.process(formdata)
大概都能猜到field.process(formdata)幹了一件什麼事,就把前端的值對應字段對象進行封裝
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])
傳值form後,接下來的就是驗證了,猜它都會循環每一個字段,調用每一個字段的驗證方法
接下來就看到LoginForm的驗證方法
def validate(self): extra = {} for name in self._fields: #獲取每一個字段的鉤子函數 validate_name validate_pwd inline = getattr(self.__class__, 'validate_%s' % name, None) if inline is not None: extra[name] = [inline] ''' 前提是你有定義這麼一個函數 添加完後extra = { name : [validate_name], pwd : [validate_pwd] } ''' return super(Form, self).validate(extra)
而在執行父類裏的驗證方法裏,就負責調用了每一個字段裏的驗證方法
def validate(self, extra_validators=None): self._errors = None success = True ''' extra_validators = { name : [validate_name], pwd : [validate_pwd] } ''' 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() #調用字段的驗證方法,並傳入鉤子函數,self爲form對象,到時候用它裏面的正則和要驗證的值 if not field.validate(self, extra): success = False return success
在字段的驗證方法中,有這麼一段,調用了_run_validation_chain方法
if not stop_validation: ''' validators=[ validators.DataRequired(message='用戶名不能爲空.'), validators.Length(min=6, max=18, message='用戶名長度必須大於%(min)d且小於%(max)d') ] extra_validators=[鉤子函數] ''' chain = itertools.chain(self.validators, extra_validators) stop_validation = self._run_validation_chain(form, chain)
執行_run_validation_chain
#循環每條驗證規則進行驗證 for validator in validators: try: #validator要麼是鉤子函數 要麼是對象,對象則又去執行__call__方法 validator(form, self) 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
分析實例代碼
#!/usr/bin/env python # -*- coding:utf-8 -*- from flask import Flask, render_template, request, redirect from wtforms import Form from wtforms.fields import simple from wtforms import validators from wtforms import widgets app = Flask(__name__, template_folder='templates') app.debug = True # 1.因爲 metaclass=FormMeta,因此LoginForm是由FormMeta建立 # 2. 執行 FormMeta.__init__ # LoginForm._unbound_fields = None # LoginForm._wtforms_meta = None # 3. 解釋字段: # name = simple.StringField(...) # pwd = simple.PasswordField(...) # 結果: # LoginForm.name = UnboundField(simple.StringField,StringField的全部參數) # LoginForm.pwd = UnboundField(simple.PasswordField,PasswordField的全部參數) 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'} ) def validate_name(self,form): pass """ print(LoginForm.__dict__) LoginForm ={ '__module__': '__main__', 'name': <1 UnboundField(StringField, (), {'label': '用戶名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>, 'pwd': <2 UnboundField(PasswordField, (), {'label': '密碼', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, '__doc__': None, '_unbound_fields': None, '_wtforms_meta': None } """ @app.route('/login', methods=['GET', 'POST']) def login(): # 實例LoginForm # 1. 執行FormMeta的__call__方法 """ class Meta(DefaultMeta,): pass LoginForm ={ '__module__': '__main__', 'name': <2 UnboundField(StringField, (), {'label': '用戶名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>, 'pwd': <1 UnboundField(PasswordField, (), {'label': '密碼', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, '__doc__': None, '_unbound_fields': [ (name, UnboundField對象(1,simple.StringField,參數)), (pwd, UnboundField對象(2,simple.PasswordField,參數)), ], '_wtforms_meta': Meta } """ # 2. 執行LoginForm的__new__方法 # pass # 3. 執行LoginForm的__init__方法 """ LoginForm ={ '__module__': '__main__', 'name': <2 UnboundField(StringField, (), {'label': '用戶名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>, 'pwd': <1 UnboundField(PasswordField, (), {'label': '密碼', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, '__doc__': None, '_unbound_fields': [ (name, UnboundField對象(1,simple.StringField,參數)), (pwd, UnboundField對象(2,simple.PasswordField,參數)), ], '_wtforms_meta': Meta } form = { _fields: { name: StringField對象(), pwd: PasswordField對象(), } name: StringField對象(widget=widgets.TextInput()), pwd: PasswordField對象(widget=widgets.PasswordInput()) } """ if request.method == 'GET': form = LoginForm() # form._fields['name'] # form.name = StringField對象() """ 1. StringField對象.__str__ 2. StringField對象.__call__ 3. meta.render_field(StringField對象,) 4. StringField對象.widget(field, **render_kw) 5. 插件.__call__() """ print(form.name) # """ 0. Form.__iter__: 返回全部字段對象 1. StringField對象.__str__ 2. StringField對象.__call__ 3. meta.render_field(StringField對象,) 4. StringField對象.widget(field, **render_kw) 5. 插件.__call__() """ for item in form: # item是fields中的每個字段 print(item) return render_template('login.html',form=form) else: # 上述流程+ # 從請求中獲取每一個值,再複製到到每一個字段對象中 """ form = { _fields: { name: StringField對象(data=你輸入的用戶名), pwd: PasswordField對象(pwd=你輸入的密碼), } name: StringField對象(widget=widgets.TextInput(data=你輸入的用戶名)), pwd: PasswordField對象(widget=widgets.PasswordInput(pwd=你輸入的密碼)) } """ # 請求發過來的值 form = LoginForm(formdata=request.form) # 值.getlist('name') # 實例:編輯 # # 從數據庫對象 # form = LoginForm(obj='值') # 值.name/值.pwd # # # 字典 {} # form = LoginForm(data=request.form) # 值['name'] # 1. 循環全部的字段 # 2. 獲取每一個字段的鉤子函數 # 3. 爲每一個字段執行他的驗證流程 字段.validate(鉤子函數+內置驗證規則) if form.validate(): print(form.data) else: print(form.errors) if __name__ == '__main__': app.run()