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