Flask組件之wtforms

一、簡介

  WTForms是一個支持多個web框架的form組件,主要用於對用戶請求數據進行驗證。html

  安裝:html5

pip3 install wtforms

二、用戶登陸註冊示例

2.一、用戶登陸

  當用戶登陸時候,須要對用戶提交的用戶名和密碼進行多種格式校驗。如:python

  用戶不能爲空;用戶長度必須大於6;

  密碼不能爲空;密碼長度必須大於12;密碼必須包含 字母、數字、特殊字符等(自定義正則);web

 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.二、用戶註冊

  註冊頁面須要讓用戶輸入:用戶名、密碼、密碼重複、性別、愛好等flask

  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.三、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.一、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
相關文章
相關標籤/搜索