Flask - WTForms

簡介

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

 

安裝:

pip3 install wtforms

 

用戶登陸註冊示例

1. 用戶登陸

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

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

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

 

 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()
56 
57 app.py
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>
19 
20 login.html
login.html

 

2. 用戶註冊

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

  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()
125 
126 app.py
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>
17 
18 register.html
register.html

 

3. meta

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect, session
from wtforms import Form
from wtforms.csrf.core import CSRF
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
from hashlib import md5

app = Flask(__name__, template_folder='templates')
app.debug = True


class MyCSRF(CSRF):
    """
    Generate a CSRF token based on the user's IP. I am probably not very
    secure, so don't use me.
    """

    def setup_form(self, form):
        self.csrf_context = form.meta.csrf_context()
        self.csrf_secret = form.meta.csrf_secret
        return super(MyCSRF, self).setup_form(form)

    def generate_csrf_token(self, csrf_token):
        gid = self.csrf_secret + self.csrf_context
        token = md5(gid.encode('utf-8')).hexdigest()
        return token

    def validate_csrf_token(self, form, field):
        print(field.data, field.current_token)
        if field.data != field.current_token:
            raise ValueError('Invalid CSRF')


class TestForm(Form):
    name = html5.EmailField(label='用戶名')
    pwd = simple.StringField(label='密碼')

    class Meta:
        # -- CSRF
        # 是否自動生成CSRF標籤
        csrf = True
        # 生成CSRF標籤name
        csrf_field_name = 'csrf_token'

        # 自動生成標籤的值,加密用的csrf_secret
        csrf_secret = 'xxxxxx'
        # 自動生成標籤的值,加密用的csrf_context
        csrf_context = lambda x: request.url
        # 生成和比較csrf標籤
        csrf_class = MyCSRF

        # -- i18n
        # 是否支持本地化
        # locales = False
        locales = ('zh', 'en')
        # 是否對本地化進行緩存
        cache_translations = True
        # 保存本地化緩存信息的字段
        translations_cache = {}


@app.route('/index/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        form = TestForm()
    else:
        form = TestForm(formdata=request.form)
        if form.validate():
            print(form)
    return render_template('index.html', form=form)


if __name__ == '__main__':
    app.run()

 

4.鉤子函數

 1 class LoginForm(Form):
 2     name = simple.StringField(
 3         validators=[
 4             validators.DataRequired(message='用戶名不能爲空.'),
 5         ],
 6         widget=widgets.TextInput(),
 7         render_kw={'placeholder': '請輸入用戶名'}
 8     )
 9     pwd = simple.PasswordField(
10         validators=[
11             validators.DataRequired(message='密碼不能爲空.'),
12 
13         ],
14         render_kw={'placeholder': '請輸入密碼'}
15     )
16     
17     # 鉤子函數
18     def validate_name(self, field):
19         """
20         自定義name字段規則
21         :param field:
22         :return:
23         """
24         # 最開始初始化時,self.data中已經有全部的值
25         print('鉤子函數獲取的值', field.data)
26         if not field.data.startswith('old'):
27             raise validators.ValidationError("用戶名必須以old開頭") # 繼續後續驗證
28             # raise validators.StopValidation("用戶名必須以old開頭")  # 再也不繼續後續驗證
View Code

 

其餘:

1. metaclass

示例一:flask

class MyType(type):
    def __init__(self, *args, **kwargs):
        print('MyType建立類',self)
        super(MyType, self).__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        obj = super(MyType, self).__call__(*args, **kwargs)
        print('類建立對象', self, obj)
        return obj


class Foo(object,metaclass=MyType):
    user = 'wupeiqi'
    age = 18

obj = Foo()

示例一

  

示例二:
class MyType(type):
    def __init__(self, *args, **kwargs):
        super(MyType, self).__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        v = dir(cls)
        obj = super(MyType, cls).__call__(*args, **kwargs)
        return obj


class Foo(MyType('MyType', (object,), {})):
    user = 'wupeiqi'
    age = 18


obj = Foo()

示例二

  

示例三:
class MyType(type):
    def __init__(self, *args, **kwargs):
        super(MyType, self).__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        v = dir(cls)
        obj = super(MyType, cls).__call__(*args, **kwargs)
        return obj


def with_metaclass(arg,base):
    return MyType('MyType', (base,), {})


class Foo(with_metaclass(MyType,object)):
    user = 'wupeiqi'
    age = 18


obj = Foo()

示例三

 

2. 實例化流程分析

# 源碼流程
    1. 執行type的 __call__ 方法,讀取字段到靜態字段 cls._unbound_fields 中; meta類讀取到cls._wtforms_meta中
    2. 執行構造方法
        
        a. 循環cls._unbound_fields中的字段,並執行字段的bind方法,而後將返回值添加到 self._fields[name] 中。
            即:
                _fields = {
                    name: wtforms.fields.core.StringField(),
                }
                
            PS:因爲字段中的__new__方法,實例化時:name = simple.StringField(label='用戶名'),建立的是UnboundField(cls, *args, **kwargs),當執行完bind以後,才變成執行 wtforms.fields.core.StringField()
        
        b. 循環_fields,爲對象設置屬性
            for name, field in iteritems(self._fields):
                # Set all the fields to attributes so that they obscure the class
                # attributes with the same names.
                setattr(self, name, field)
        c. 執行process,爲字段設置默認值:self.process(formdata, obj, data=data, **kwargs)
            優先級:obj,data,formdata;
            
            再循環執行每一個字段的process方法,爲每一個字段設置值:
            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:
                    field.process(formdata)
            
            執行每一個字段的process方法,爲字段的data和字段的raw_data賦值
            def process(self, formdata, data=unset_value):
                self.process_errors = []
                if data is unset_value:
                    try:
                        data = self.default()
                    except TypeError:
                        data = self.default
        
                self.object_data = data
        
                try:
                    self.process_data(data)
                except ValueError as e:
                    self.process_errors.append(e.args[0])
        
                if formdata:
                    try:
                        if self.name in formdata:
                            self.raw_data = formdata.getlist(self.name)
                        else:
                            self.raw_data = []
                        self.process_formdata(self.raw_data)
                    except ValueError as e:
                        self.process_errors.append(e.args[0])
        
                try:
                    for filter in self.filters:
                        self.data = filter(self.data)
                except ValueError as e:
                    self.process_errors.append(e.args[0])
                
        d. 頁面上執行print(form.name) 時,打印標籤
            
            由於執行了:
                字段的 __str__ 方法
                字符的 __call__ 方法
                self.meta.render_field(self, kwargs)
                    def render_field(self, 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)
                執行字段的插件對象的 __call__ 方法,返回標籤字符串

  

3. 驗證流程分析

a. 執行form的validate方法,獲取鉤子方法
            def validate(self):
                extra = {}
                for name in self._fields:
                    inline = getattr(self.__class__, 'validate_%s' % name, None)
                    if inline is not None:
                        extra[name] = [inline]
        
                return super(Form, self).validate(extra)
        b. 循環每個字段,執行字段的 validate 方法進行校驗(參數傳遞了鉤子函數)
            def validate(self, extra_validators=None):
                self._errors = None
                success = True
                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()
                    if not field.validate(self, extra):
                        success = False
                return success
        c. 每一個字段進行驗證時候
            字段的pre_validate 【預留的擴展】
            字段的_run_validation_chain,對正則和字段的鉤子函數進行校驗
            字段的post_validate【預留的擴展】
相關文章
相關標籤/搜索