Flask-WTForms

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

wtforms
做用: 1.生成HTML標籤(執行類裏面的__str__返回的是input標籤的字符串)
    2.對用戶請求的數據進行校驗
使用:
  -用戶登陸
  -用戶註冊
  - 數據庫獲取數據而且實時更新 (重寫構造方法__init__) 對於Django中form組件也是同樣的。html5

 

用戶登陸註冊示例

1. 用戶登陸python

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

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

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

app.py數據庫

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

 

login.htmlflask

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登陸</h1>
<form method="post">
    <!--<input type="text" name="name">-->
    <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>

    <!--<input type="password" name="pwd">-->
    <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
    <input type="submit" value="提交">
</form>
</body>
</html>

  

 

2. 用戶註冊緩存

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

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
    )
    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=session.query(Favor.id,Favor.name).all()  #動態的更新數據從數據庫中 SQLalchemy
                                          #DBUtils  sqlhelper.fetchall('select id,name from td1',[])      
    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': 1})
        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()

app.py

  

示例下載:點擊這裏 app

其餘:

1. metaclass

	-類是由什麼建立的(由 type)
				
					建立類時 先執行 type 的__init__方法 ----> 用來建立類
					
					當一個類在實例化時執行type的__call__方法,
					__call__方法的返回值就是當前類的實例化的對象;
					在__call__方法中:	                        
						-先調用當時實例化這個類的__new__,建立對象;將這個對象進行返回
						-再調用類的__init__,對象的初始化。

  

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

  

 

 

 

因此如今的類有兩種建立的方式:

類有兩種建立的方式:
					1.class Foo(object):pass
					2.type('Bar',(object),{})

class Foo(object):pass
f = Foo()
這個類執行的流程:
1.先執行type的__init__方法建立當前的類
2.實例化當前類的時候,調用了type的__call__方法。
3.在type的__call__方法中調用當前類的__new__返回一個對象;而後調用__init__作初始化。

-在wtforms 的源碼裏面體現的:

		-源碼
			wtform中    類的建立
							- type.__init__
					    對象的建立		
					    	- type__call__
					    		- cls.__new__
					    		- cls.__init__
				class Loginform(Form):
						name = simple.StringField(
							label='用戶名',
							validators=[
								validators.DataRequired(message='用戶名不能爲空')
							],
							render_kw={"class":'input-material'}
						)			    		
				'''
				name = simple.StringField()
				這裏simple.StringField也是個類,沒有metaclass方法,因此這個類是由type下的__init__方法所建立的
				simple.StringField() --> 先執行__new__,再執行 __init__ 
				simple.StringField()裏面沒有傳入參數 --->  return UnboundField(cls, *args, **kwargs)
				name = UnboundField(creation_counter=1,simple.StringField)
				'''				    		
				form = Loginform()  
				print(form.name,type(form.name))

				'''
				實例化Loginform類的時候,也是就 FormMet對象() --> 執行FormMet類的__call__方法->type.__call__(cls, *args, **kwargs)
				--->調用Loginform的__new__ ,__init__方法
				FormMet類的__call__方法:
					Loginform._unbound_fields = None
					Loginform._wtforms_meta = None          dir(Loginform)獲取這個類裏面的所有內容
					Loginform.name = UnboundField(simple.StringField)
					
					unbound_field = getattr(cls, name) 
					unbound_field = UnboundField(simple.StringField)
					UnboundField這個類中有
						 _formfield = True;
						 UnboundField.creation_counter += 1;這個是用來幫咱們計數 從而排序的
					
					Loginform._unbound_fields=[
						('name', UnboundField(simple.StringField))             
					
					]
					
					這列表裏面的數據是經過creation_counter進行排完順序的。
					都沒有__new__方法就執行object的new方法返回對象
				;
				
				Loginform__init__方法:
					 super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
					    fields=._unbound_fields
					 -for name, unbound_field in itertools.chain(fields, extra_fields):
						
						_fields=OrderedDict{
							'name': simple.StringField()
						}
					
					for name, field in iteritems(self._fields):
						setattr(self, name, field)
						--> self.name = simple.StringField()
				'''

  

class Form(with_metaclass(FormMeta, BaseForm)): with_metaclass(FormMeta, BaseForm)--->NewBase

def with_metaclass(meta, base=object): ----> class NewBase(BaseForm,metaclass=FormMeta):
pass
return meta("NewBase", (base,), {})

  

wtform的源碼流程:

''''
wtform的源碼流程:
首先建立Loginform這個類
本身沒有metaclass,可是父類有metaclass(FormMeta(type))
全部執行FormMeta下面的__init__方法建立Loginform類


Loginform._unbound_fields = None
Loginform._wtforms_meta = None

'''


class Loginform(Form):
	name = simple.StringField(
		label='用戶名',
		validators=[
			validators.DataRequired(message='用戶名不能爲空')
		],
		render_kw={"class":'input-material'}
	)
	'''
	name = simple.StringField()
	這裏simple.StringField也是個類,沒有metaclass方法,因此這個類是由type下的__init__方法所建立的
	simple.StringField() --> 先執行__new__,再執行 __init__ 
	simple.StringField()裏面沒有傳入參數 --->  return UnboundField(cls, *args, **kwargs)
	name = UnboundField(creation_counter=1,simple.StringField)
	'''
	pwd = simple.StringField(
		label='密碼',
		widget=widgets.PasswordInput(),
		validators=[
			validators.DataRequired(message='密碼不能爲空')
		],
		render_kw={"class": 'input-material'}
	)

	agree = core.StringField(
		widget=widgets.CheckboxInput(),
		validators=[
			validators.DataRequired(message='必需要肯定')
		],
		render_kw={"class": 'checkbox-template',"value":"1"}
	)
from flask import Blueprint, request, session, render_template, redirect, url_for
from crm.utils.sqlhelper import fetchone, insert
from crm.utils.pwdmd5 import getmd5
from crm.utils.account_form import Loginform, Registerform

ac = Blueprint("ac", __name__)


@ac.route("/login", methods=["GET", "POST"], )
def login():


	if request.method == "GET":
		form = Loginform()
		print(form.name,type(form.name))

		'''
		實例化Loginform類的時候,也是就 FormMet對象() --> 執行FormMet類的__call__方法->type.__call__(cls, *args, **kwargs)
		--->調用Loginform的__new__ ,__init__方法
		FormMet類的__call__方法:
			Loginform._unbound_fields = None
			Loginform._wtforms_meta = None          dir(Loginform)獲取這個類裏面的所有內容
			Loginform.name = UnboundField(simple.StringField)
			
			unbound_field = getattr(cls, name) 
			unbound_field = UnboundField(simple.StringField)
			UnboundField這個類中有
				 _formfield = True;
				 UnboundField.creation_counter += 1;這個是用來幫咱們計數 從而排序的
			
			Loginform._unbound_fields=[
				('name', UnboundField(simple.StringField))             
			
			]
			
			這列表裏面的數據是經過creation_counter進行排完順序的。
			
		都沒有__new__方法就執行object的new方法返回對象;
		
		Loginform__init__方法:
			 super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
			    fields=._unbound_fields
			 -for name, unbound_field in itertools.chain(fields, extra_fields):
				
				_fields=OrderedDict{
					'name': simple.StringField()
				}
			
			for name, field in iteritems(self._fields):
				setattr(self, name, field)
				--> self.name = simple.StringField()
		'''


		return render_template("login.html", error="", form=form)

 

-源碼補充
  - Meta (設置crsf)

  

#!/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()

  


  - 鉤子函數(validate_字段名,)
    

    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("密碼不一致") # 再也不繼續後續驗證

 

  

詳細點我

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息