Flask组件之wtforms

1、简介

  WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证。

  安装:

pip3 install wtforms

2、用户登录注册示例

2.1、用户登录

  当用户登录时候,需要对用户提交的用户名和密码进行多种格式校验。如:

  用户不能为空;用户长度必须大于6;

  密码不能为空;密码长度必须大于12;密码必须包含 字母、数字、特殊字符等(自定义正则);

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 from flask import Flask, render_template, request, redirect
 4 from wtforms import Form
 5 from wtforms.fields import core
 6 from wtforms.fields import html5
 7 from wtforms.fields import simple
 8 from wtforms import validators
 9 from wtforms import widgets
10 
11 app = Flask(__name__, template_folder='templates')
12 app.debug = True
13 
14 
15 class LoginForm(Form):
16     name = simple.StringField(
17         label='用户名',
18         validators=[
19             validators.DataRequired(message='用户名不能为空.'),
20             validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
21         ],
22         widget=widgets.TextInput(),
23         render_kw={'class': 'form-control'}
24 
25     )
26     pwd = simple.PasswordField(
27         label='密码',
28         validators=[
29             validators.DataRequired(message='密码不能为空.'),
30             validators.Length(min=8, message='用户名长度必须大于%(min)d'),
31             validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}",
32                               message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')
33 
34         ],
35         widget=widgets.PasswordInput(),
36         render_kw={'class': 'form-control'}
37     )
38 
39 
40 
41 @app.route('/login', methods=['GET', 'POST'])
42 def login():
43     if request.method == 'GET':
44         form = LoginForm()
45         return render_template('login.html', form=form)
46     else:
47         form = LoginForm(formdata=request.form)
48         if form.validate():
49             print('用户提交数据通过格式验证,提交的值为:', form.data)
50         else:
51             print(form.errors)
52         return render_template('login.html', form=form)
53 
54 if __name__ == '__main__':
55     app.run()
app.py
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Title</title>
 6 </head>
 7 <body>
 8 <h1>登录</h1>
 9 <form method="post">
10     <!--<input type="text" name="name">-->
11     <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>
12 
13     <!--<input type="password" name="pwd">-->
14     <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
15     <input type="submit" value="提交">
16 </form>
17 </body>
18 </html>
login.html

2.2、用户注册

  注册页面需要让用户输入:用户名、密码、密码重复、性别、爱好等

  1 from flask import Flask, render_template, request, redirect
  2 from wtforms import Form
  3 from wtforms.fields import core
  4 from wtforms.fields import html5
  5 from wtforms.fields import simple
  6 from wtforms import validators
  7 from wtforms import widgets
  8 
  9 app = Flask(__name__, template_folder='templates')
 10 app.debug = True
 11 
 12 
 13 
 14 class RegisterForm(Form):
 15     name = simple.StringField(
 16         label='用户名',
 17         validators=[
 18             validators.DataRequired()
 19         ],
 20         widget=widgets.TextInput(),
 21         render_kw={'class': 'form-control'},
 22         default='alex'
 23     )
 24 
 25     pwd = simple.PasswordField(
 26         label='密码',
 27         validators=[
 28             validators.DataRequired(message='密码不能为空.')
 29         ],
 30         widget=widgets.PasswordInput(),
 31         render_kw={'class': 'form-control'}
 32     )
 33 
 34     pwd_confirm = simple.PasswordField(
 35         label='重复密码',
 36         validators=[
 37             validators.DataRequired(message='重复密码不能为空.'),
 38             validators.EqualTo('pwd', message="两次密码输入不一致")
 39         ],
 40         widget=widgets.PasswordInput(),
 41         render_kw={'class': 'form-control'}
 42     )
 43 
 44     email = html5.EmailField(
 45         label='邮箱',
 46         validators=[
 47             validators.DataRequired(message='邮箱不能为空.'),
 48             validators.Email(message='邮箱格式错误')
 49         ],
 50         widget=widgets.TextInput(input_type='email'),
 51         render_kw={'class': 'form-control'}
 52     )
 53 
 54     gender = core.RadioField(
 55         label='性别',
 56         choices=(
 57             (1, ''),
 58             (2, ''),
 59         ),
 60         coerce=int
 61     )
 62     city = core.SelectField(
 63         label='城市',
 64         choices=(
 65             ('bj', '北京'),
 66             ('sh', '上海'),
 67         )
 68     )
 69 
 70     hobby = core.SelectMultipleField(
 71         label='爱好',
 72         choices=(
 73             (1, '篮球'),
 74             (2, '足球'),
 75         ),
 76         coerce=int
 77     )
 78 
 79     favor = core.SelectMultipleField(
 80         label='喜好',
 81         choices=(
 82             (1, '篮球'),
 83             (2, '足球'),
 84         ),
 85         widget=widgets.ListWidget(prefix_label=False),
 86         option_widget=widgets.CheckboxInput(),
 87         coerce=int,
 88         default=[1, 2]
 89     )
 90 
 91     def __init__(self, *args, **kwargs):
 92         super(RegisterForm, self).__init__(*args, **kwargs)
 93         self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))
 94 
 95     def validate_pwd_confirm(self, field):
 96         """
 97         自定义pwd_confirm字段规则,例:与pwd字段是否一致
 98         :param field: 
 99         :return: 
100         """
101         # 最开始初始化时,self.data中已经有所有的值
102 
103         if field.data != self.data['pwd']:
104             # raise validators.ValidationError("密码不一致") # 继续后续验证
105             raise validators.StopValidation("密码不一致")  # 不再继续后续验证
106 
107 
108 @app.route('/register', methods=['GET', 'POST'])
109 def register():
110     if request.method == 'GET':
111         form = RegisterForm(data={'gender': 1})
112         return render_template('register.html', form=form)
113     else:
114         form = RegisterForm(formdata=request.form)
115         if form.validate():
116             print('用户提交数据通过格式验证,提交的值为:', form.data)
117         else:
118             print(form.errors)
119         return render_template('register.html', form=form)
120 
121 
122 
123 if __name__ == '__main__':
124     app.run()
app.py
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Title</title>
 6 </head>
 7 <body>
 8 <h1>用户注册</h1>
 9 <form method="post" novalidate style="padding:0  50px">
10     {% for item in form %}
11     <p>{{item.label}}: {{item}} {{item.errors[0] }}</p>
12     {% endfor %}
13     <input type="submit" value="提交">
14 </form>
15 </body>
16 </html>
register.html

2.3、meta

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 from flask import Flask, render_template, request, redirect, session
 4 from wtforms import Form
 5 from wtforms.csrf.core import CSRF
 6 from wtforms.fields import core
 7 from wtforms.fields import html5
 8 from wtforms.fields import simple
 9 from wtforms import validators
10 from wtforms import widgets
11 from hashlib import md5
12 
13 app = Flask(__name__, template_folder='templates')
14 app.debug = True
15 
16 
17 class MyCSRF(CSRF):
18     """
19     Generate a CSRF token based on the user's IP. I am probably not very
20     secure, so don't use me.
21     """
22 
23     def setup_form(self, form):
24         self.csrf_context = form.meta.csrf_context()
25         self.csrf_secret = form.meta.csrf_secret
26         return super(MyCSRF, self).setup_form(form)
27 
28     def generate_csrf_token(self, csrf_token):
29         gid = self.csrf_secret + self.csrf_context
30         token = md5(gid.encode('utf-8')).hexdigest()
31         return token
32 
33     def validate_csrf_token(self, form, field):
34         print(field.data, field.current_token)
35         if field.data != field.current_token:
36             raise ValueError('Invalid CSRF')
37 
38 
39 class TestForm(Form):
40     name = html5.EmailField(label='用户名')
41     pwd = simple.StringField(label='密码')
42 
43     class Meta:
44         # -- CSRF
45         # 是否自动生成CSRF标签
46         csrf = True
47         # 生成CSRF标签name
48         csrf_field_name = 'csrf_token'
49 
50         # 自动生成标签的值,加密用的csrf_secret
51         csrf_secret = 'xxxxxx'
52         # 自动生成标签的值,加密用的csrf_context
53         csrf_context = lambda x: request.url
54         # 生成和比较csrf标签
55         csrf_class = MyCSRF
56 
57         # -- i18n
58         # 是否支持本地化
59         # locales = False
60         locales = ('zh', 'en')
61         # 是否对本地化进行缓存
62         cache_translations = True
63         # 保存本地化缓存信息的字段
64         translations_cache = {}
65 
66 
67 @app.route('/index/', methods=['GET', 'POST'])
68 def index():
69     if request.method == 'GET':
70         form = TestForm()
71     else:
72         form = TestForm(formdata=request.form)
73         if form.validate():
74             print(form)
75     return render_template('index.html', form=form)
76 
77 
78 if __name__ == '__main__':
79     app.run()
View Code

3、其他

3.1、metaclass

 1 class MyType(type):
 2     def __init__(self, *args, **kwargs):
 3         print('MyType创建类',self)
 4         super(MyType, self).__init__(*args, **kwargs)
 5 
 6     def __call__(self, *args, **kwargs):
 7         obj = super(MyType, self).__call__(*args, **kwargs)
 8         print('类创建对象', self, obj)
 9         return obj
10 
11 
12 class Foo(object,metaclass=MyType):
13     user = 'wupeiqi'
14     age = 18
15 
16 obj = Foo()
示例一
 1 class MyType(type):
 2     def __init__(self, *args, **kwargs):
 3         super(MyType, self).__init__(*args, **kwargs)
 4 
 5     def __call__(cls, *args, **kwargs):
 6         v = dir(cls)
 7         obj = super(MyType, cls).__call__(*args, **kwargs)
 8         return obj
 9 
10 
11 class Foo(MyType('MyType', (object,), {})):
12     user = 'wupeiqi'
13     age = 18
14 
15 
16 obj = Foo()
示例二
 1 class MyType(type):
 2     def __init__(self, *args, **kwargs):
 3         super(MyType, self).__init__(*args, **kwargs)
 4 
 5     def __call__(cls, *args, **kwargs):
 6         v = dir(cls)
 7         obj = super(MyType, cls).__call__(*args, **kwargs)
 8         return obj
 9 
10 
11 def with_metaclass(arg,base):
12     return MyType('MyType', (base,), {})
13 
14 
15 class Foo(with_metaclass(MyType,object)):
16     user = 'wupeiqi'
17     age = 18
18 
19 
20 obj = Foo()
示例三

3.2. 实例化流程分析

 1 # 源码流程
 2     1. 执行type的 __call__ 方法,读取字段到静态字段 cls._unbound_fields 中; meta类读取到cls._wtforms_meta中
 3     2. 执行构造方法
 4         
 5         a. 循环cls._unbound_fields中的字段,并执行字段的bind方法,然后将返回值添加到 self._fields[name] 中。
 6             即:
 7                 _fields = {
 8                     name: wtforms.fields.core.StringField(),
 9                 }
10                 
11             PS:由于字段中的__new__方法,实例化时:name = simple.StringField(label='用户名'),创建的是UnboundField(cls, *args, **kwargs),当执行完bind之后,才变成执行 wtforms.fields.core.StringField()
12         
13         b. 循环_fields,为对象设置属性
14             for name, field in iteritems(self._fields):
15                 # Set all the fields to attributes so that they obscure the class
16                 # attributes with the same names.
17                 setattr(self, name, field)
18         c. 执行process,为字段设置默认值:self.process(formdata, obj, data=data, **kwargs)
19             优先级:obj,data,formdata;
20             
21             再循环执行每个字段的process方法,为每个字段设置值:
22             for name, field, in iteritems(self._fields):
23                 if obj is not None and hasattr(obj, name):
24                     field.process(formdata, getattr(obj, name))
25                 elif name in kwargs:
26                     field.process(formdata, kwargs[name])
27                 else:
28                     field.process(formdata)
29             
30             执行每个字段的process方法,为字段的data和字段的raw_data赋值
31             def process(self, formdata, data=unset_value):
32                 self.process_errors = []
33                 if data is unset_value:
34                     try:
35                         data = self.default()
36                     except TypeError:
37                         data = self.default
38         
39                 self.object_data = data
40         
41                 try:
42                     self.process_data(data)
43                 except ValueError as e:
44                     self.process_errors.append(e.args[0])
45         
46                 if formdata:
47                     try:
48                         if self.name in formdata:
49                             self.raw_data = formdata.getlist(self.name)
50                         else:
51                             self.raw_data = []
52                         self.process_formdata(self.raw_data)
53                     except ValueError as e:
54                         self.process_errors.append(e.args[0])
55         
56                 try:
57                     for filter in self.filters:
58                         self.data = filter(self.data)
59                 except ValueError as e:
60                     self.process_errors.append(e.args[0])
61                 
62         d. 页面上执行print(form.name) 时,打印标签
63             
64             因为执行了:
65                 字段的 __str__ 方法
66                 字符的 __call__ 方法
67                 self.meta.render_field(self, kwargs)
68                     def render_field(self, field, render_kw):
69                         other_kw = getattr(field, 'render_kw', None)
70                         if other_kw is not None:
71                             render_kw = dict(other_kw, **render_kw)
72                         return field.widget(field, **render_kw)
73                 执行字段的插件对象的 __call__ 方法,返回标签字符串
View Code

3.3.验证流程分析

 1 a. 执行form的validate方法,获取钩子方法
 2             def validate(self):
 3                 extra = {}
 4                 for name in self._fields:
 5                     inline = getattr(self.__class__, 'validate_%s' % name, None)
 6                     if inline is not None:
 7                         extra[name] = [inline]
 8         
 9                 return super(Form, self).validate(extra)
10         b. 循环每一个字段,执行字段的 validate 方法进行校验(参数传递了钩子函数)
11             def validate(self, extra_validators=None):
12                 self._errors = None
13                 success = True
14                 for name, field in iteritems(self._fields):
15                     if extra_validators is not None and name in extra_validators:
16                         extra = extra_validators[name]
17                     else:
18                         extra = tuple()
19                     if not field.validate(self, extra):
20                         success = False
21                 return success
22         c. 每个字段进行验证时候
23             字段的pre_validate 【预留的扩展】
24             字段的_run_validation_chain,对正则和字段的钩子函数进行校验
25             字段的post_validate【预留的扩展】
View Code

 

posted @ 2018-12-07 10:41  RobotsRising  阅读(254)  评论(0编辑  收藏  举报