Flask-WTF是簡化了WTForms操做的一個第三方庫。WTForms表單的兩個主要功能是驗證用戶提交數據的合法性以及渲染模板。還有其它一些功能:CSRF保護,html
文件上傳等。安裝方法:前端
pip3 install flask-wtf
1. 用戶登陸html5
當用戶登陸時候,須要對用戶提交的用戶名和密碼進行多種格式校驗。如:python
密碼不能爲空;密碼長度必須大於12;密碼必須包含 字母、數字、特殊字符等(自定義正則);正則表達式
#!/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 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()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登陸</h1> <form method="post"> <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p> <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p> <input type="submit" value="提交"> </form> </body> </html>
2. 用戶註冊flask
註冊頁面須要讓用戶輸入:用戶名、密碼、密碼重複、性別、愛好等。app
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 # 「1」 「2」 ) 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': 2,'hobby':[1,]}) # initial 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()
<!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 field in form %} <p>{{field.label}}: {{field}} {{field.errors[0] }}</p> {% endfor %} <input type="submit" value="提交"> </form> </body> </html>
流程圖ide
類的建立過程分析源碼分析
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'} )
LoginForm繼承Form post
class Form(with_metaclass(FormMeta, BaseForm)): """ Declarative Form base class. Extends BaseForm's core behaviour allowing fields to be defined on Form subclasses as class attributes. In addition, form and instance input data are taken at construction time and passed to `process()`. """ Meta = DefaultMeta
def with_metaclass(meta, base=object): return meta("NewBase", (base,), {})
咱們能夠看到 with_metaclass 經過FormMeta(繼承type) 動態的建立了一個類,這時候會執行 FormMeta的__init__方法
class FormMeta(type):
# cls是LoginForm類
def __init__(cls, name, bases, attrs): type.__init__(cls, name, bases, attrs) cls._unbound_fields = None cls._wtforms_meta = None
經過以上類的建立,咱們自定義的類多了兩個屬性
LoginForm._unbound_fields = None LoginForm._wtforms_meta = None
對LoginForm中的字段 name 進行追蹤
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'} )
咱們能夠看到name是一個對象,先追蹤其__new__方法
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)
經過上面的源碼解讀,咱們知道因爲屬性沒有_form和_name因此其返回的是 UnboundField(cls, *args, **kwargs) 對象
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
結果
LoginForm.name = UnboundField(simple.StringField,StringField的全部參數)LoginForm.pwd = UnboundField(simple.PasswordField,PasswordField的全部參數)
自動生成HTML
前端調用LoginForm.name如何生成input框的呢,首先咱們能夠看出LoginForm中的name是一個對象
class StringField(Field): """ This field is the base for most of the more complicated fields, and represents an ``<input type="text">``. """ widget = widgets.TextInput() def process_formdata(self, valuelist): if valuelist: self.data = valuelist[0] elif self.data is None: self.data = '' def _value(self): return text_type(self.data) if self.data is not None else ''
執行LoginForm.name會觸發其內部的__str__方法
def __str__(self): """ Returns a HTML representation of the field. For more powerful rendering, see the `__call__` method. """ return self()
咱們能夠看到返回的是本身,會觸發其__call__方法
def __call__(self, **kwargs): return self.meta.render_field(self, kwargs)
render_field方法以下所示
def render_field(self, field, render_kw): """ render_field allows customization of how widget rendering is done. The default implementation calls ``field.widget(field, **render_kw)`` """ other_kw = getattr(field, 'render_kw', None) if other_kw is not None: render_kw = dict(other_kw, **render_kw) return field.widget(field, **render_kw)
widget是一個對象,會觸發其內部的__call__方法
class StringField(Field): """ This field is the base for most of the more complicated fields, and represents an ``<input type="text">``. """ widget = widgets.TextInput()
TextInput對象
class TextInput(Input): """ Render a single-line text input. """ input_type = 'text'
其__call__方法以下所示
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))