Flask 的表單驗(yàn)證器
在 Web 頁面中,表單是一種常見的元素,表單包含有多個(gè)字段,通常字段的取值需要在一定的范圍內(nèi),例如:QQ 注冊(cè)時(shí),名稱不可以為空,密碼的長度至少是 8 個(gè)字符,如下圖所示:

將表單提交給服務(wù)端處理時(shí),服務(wù)端需要驗(yàn)證表單中的字段的取值是否符合要求。本節(jié)學(xué)習(xí) Flask 中提供表單驗(yàn)證的功能,學(xué)習(xí)如何對(duì)表單中的字段的取值進(jìn)行有效性檢查。
1. 表單驗(yàn)證器
1.1 WTForms 和 Flask-WTF
在 Python 的 Web 開發(fā)中,WTForms 是一個(gè)靈活的表單驗(yàn)證和表單渲染的庫,它的主要功能:
- 驗(yàn)證表單中的字段的取值是否符合要求;
- 渲染輸出表單的 HTML 代碼。
WTForms 可以與任意的 Web 框架和模板引擎一起使用。 在 Flask 框架或者 Django 框架中,都可以使用 WTForms。
Flask-WTF 在 WTForms 的基礎(chǔ)上提供了一些擴(kuò)展,可以方便的在 Flask 框架中生成表單。
1.2 表單字段
WTForms 支持如下類型的表單字段:
字段類型 | 說明 |
---|---|
StringField | 文本字段 |
TextAreaField | 多行文本字段 |
PasswordField | 密碼文本字段 |
HiddenField | 隱藏文本字段 |
DateField | 文本字段,值為datetime.date格式 |
IntegerField | 文本字段,值為整數(shù) |
DecimalField | 文本字段, 值為decimal.Decimal |
FloatField | 文本字段,值為浮點(diǎn)數(shù) |
BooleanField | 復(fù)選框, 值為True 和 False |
RadioField | 一組單選框 |
SelectField | 下拉列表 |
FileField | 文件上傳字段 |
SubmitField | 表單提交按鈕 |
1.3 驗(yàn)證器
WTForms 支持如下類型的表單驗(yàn)證:
驗(yàn)證類型 | 說明 |
---|---|
驗(yàn)證電子郵件地址 | |
EqualTo | 比較兩個(gè)字段的值;常用于要求輸入兩次秘鑰進(jìn)行確認(rèn)的情況 |
Length | 驗(yàn)證輸入字符串的長度 |
NumberRange | 驗(yàn)證輸入的值在數(shù)字范圍內(nèi) |
DateRequired | 確保字段中有數(shù)據(jù) |
2. 程序功能和結(jié)構(gòu)
2.1 程序功能
在下面的小節(jié),我們將使用 WTForms 實(shí)現(xiàn)一個(gè)登錄程序,表單中包含有如下字段:
表單字段 | 說明 |
---|---|
郵件地址 | 要求符合郵件地址的格式,如 user@qq.com |
密碼 | 要求密碼至少包括 6 個(gè)字符 |
提交 | 將表單提交給服務(wù)器 |
用戶通過瀏覽器進(jìn)行登錄,界面如下所示:
用戶輸入郵件地址為 ‘tom’、密碼為 ‘123’,點(diǎn)擊登錄按鈕將表單提交給服務(wù)器,服務(wù)器進(jìn)行表單驗(yàn)證:
- 驗(yàn)證郵件地址是否規(guī)范,并顯示驗(yàn)證失敗的信息——“請(qǐng)輸入正確的郵箱” ;
- 驗(yàn)證密碼的長度,并顯示驗(yàn)證失敗的信息——“密碼至少包括 6 個(gè)字符”。
2.2 程序結(jié)構(gòu)
例子包括 2 個(gè)源文件,如下表所示:
程序 | 說明 |
---|---|
app.py | Flask 后端程序,實(shí)現(xiàn)表單驗(yàn)證 |
templates/login.html | 登錄界面的模板 |
2.3 源程序下載
3. Flask 程序 app.py
3.1 安裝庫
使用如下命令安裝相關(guān)的庫:
$ pip3 install wtforms
$ pip3 install flask-wtf
$ pip3 install email-validator
3.2 引入庫
#!/usr/bin/python3
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import DataRequired, Length, Email, ValidationError
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
在這個(gè)程序中,引入如下類:
類 | 來自于模塊 | 功能 |
---|---|---|
FlaskForm | flask_wtf | 登錄表單的基類 |
StringField | wtforms | 文本字段 |
PasswordField | wtforms | 密碼文本字段 |
SubmitField | wtforms | 表單提交按鈕 |
DateRequired | wtforms | 確保字段中有數(shù)據(jù) |
Length | wtforms | 驗(yàn)證輸入字符串的長度 |
wtforms | 驗(yàn)證電子郵件地址的格式是否正確 | |
ValidationError | wtforms | 驗(yàn)證失敗時(shí),拋出此異常 |
在第 8 行,配置 Flask 應(yīng)用的選項(xiàng) ‘SECRET_KEY’ 用于防范 CSRF 攻擊,請(qǐng)參考 CSRF 攻擊的相關(guān)詞條。
3.3 定義登錄表單
class LoginForm(FlaskForm):
email = StringField(
label = '郵箱',
validators = [
DataRequired(message = '郵箱不能為空'),
Email(message = '請(qǐng)輸入正確的郵箱')
]
)
password = PasswordField(
label = '密碼',
validators =[
DataRequired(message = '密碼不能為空'),
Length(min = 6, message = '密碼至少包括 6 個(gè)字符')
]
)
submit = SubmitField('登錄')
定義類 LoginForm,它繼承于 FlaskForm,用于描述登錄界面,登錄界面是一個(gè)表單,包含有 3 個(gè)字段:
- email,顯示 label 為 ‘郵箱’,包括 2 個(gè)驗(yàn)證器:DataRequired 和 Email,message 參數(shù)為驗(yàn)證失敗的提示信息;
- password,顯示 label 為 ‘密碼’,包括 2 個(gè)驗(yàn)證器:DataRequired 和 Length,message 參數(shù)為驗(yàn)證失敗的提示信息,min = 6 表示密碼的最小長度;
- submit,提交按鈕,提交表單給服務(wù)端。
3.4 進(jìn)行表單驗(yàn)證
@app.route('/', methods=['GET', 'POST'])
def login():
form = LoginForm()
print('form.validate_on_submit() =', form.validate_on_submit())
print('form.email.label =', form.email.label)
print('form.email() = ', form.email)
print('form.email.errors =', form.email.errors)
return render_template('login.html', form=form)
app.run(debug=True)
在第 1 行,設(shè)置以 GET 方法或者 POST 方法訪問路徑 / 時(shí),使用函數(shù) login() 進(jìn)行處理;在第 3 行,創(chuàng)建一個(gè)實(shí)例 form,表示用戶登錄的表單;在第 8 行,調(diào)用 render_template 渲染 login.html。
form 對(duì)象提供了如下方法和屬性:
屬性 | 說明 |
---|---|
form.validate_on_submit() | 表單驗(yàn)證函數(shù),返回值表示驗(yàn)證是否正確 |
form.email() | 顯示 email 字段對(duì)應(yīng)的 HTML 代碼 |
form.email.label | email 字段的 label |
form.email.errors | 驗(yàn)證 email 字段的失敗提示信息 |
在程序中打印 form 的屬性,當(dāng)用戶提交表單時(shí),在控制臺(tái)中顯示如下信息:
form.validate_on_submit() = False
form.email.label = <label for="email">郵箱</label>
form.email() = <input id="email" name="email" required type="text" value="tom">
form.email.errors = ['請(qǐng)輸入正確的郵箱']
當(dāng)表單驗(yàn)證失敗時(shí),form.validate_on_submit() 返回為 False。form.email.errors 是一個(gè)列表,記錄了所有可能的錯(cuò)誤信息。
4. 模板文件 login.html
<html>
<meta charset='UTF-8'>
<h1>登錄</h1>
<form class="form" method="POST">
<div>
{{ form.email.label }}
{{ form.email() }}
<b>{{ form.email.errors[0] }}</b>
</div>
<div>
{{ form.password.label }}
{{ form.password() }}
<b>{{ form.password.errors[0] }}</b>
</div>
<div>
{{ form.submit() }}
</div>
{{ form.hidden_tag() }}
</form>
</html>
login.html 是用于描述登錄界面的模板,根據(jù) form 中字段 email 和 password 的屬性,它被渲染為如下的 HTML 文件:
<html>
<meta charset='UTF-8'>
<title>登錄</title>
<h1>登錄</h1>
<form class="form" method="POST">
<div>
<label for="email">郵箱</label>
<input id="email" name="email" required type="text" value="tom">
<b>請(qǐng)輸入正確的郵箱</b>
</div>
<div>
<label for="password">密碼</label>
<input id="password" name="password" required type="password" value="">
<b>密碼至少包括 6 個(gè)字符</b>
</div>
<div>
<input id="submit" name="submit" type="submit" value="登錄">
</div>
<input id="csrf_token" name="csrf_token" type="hidden" value="ImY2Y2NhMWNjZDRlYWE1ZDE2ODRiZDFlYzY5ZGNhNDIzZWJmNWQ0OTQi.X1Fo_w.13ad614Bw80RgPhc9RFdjZw7-q0">
</form>
</html>
這里注意兩點(diǎn):
- form.email.errors 和 form.password.errors 是一個(gè)錯(cuò)誤信息列表,errors[0] 表示第一條錯(cuò)誤信息;
- form.hidden_tag() 用于防范 CSRF 攻擊,生成 <input id=“csrf_token”/> 標(biāo)簽,請(qǐng)參考相關(guān)詞條。
5. 自定義驗(yàn)證器
5.1 簡介
在 Flask 中,可以自定義驗(yàn)證器實(shí)現(xiàn)特定的驗(yàn)證需求,例如:在驗(yàn)證密碼字段時(shí),要求密碼不能全部都是數(shù)字。
Flask 包含有兩種類型的驗(yàn)證器:行內(nèi)驗(yàn)證器和全局驗(yàn)證器。下面通過具體的例子說明如何實(shí)現(xiàn)自定義驗(yàn)證 “密碼是否全部是數(shù)字”。
5.2 行內(nèi)驗(yàn)證器
對(duì)前面小節(jié)的例子程序進(jìn)行如下的局部修改,修改類 LoginForm,增加一個(gè)成員函數(shù) validate_password,如下:
class LoginForm(FlaskForm):
def validate_password(self, field):
for char in field.data:
print('!', char)
if '0123456789'.find(char) < 0:
return
raise ValidationError('密碼不能全部是數(shù)字')
在第 2 行,定義成員函數(shù) validate_password,驗(yàn)證函數(shù)的形式為 validate_字段名,在驗(yàn)證字段數(shù)據(jù)時(shí)會(huì)調(diào)用這個(gè)方法來驗(yàn)證對(duì)應(yīng)的字段,在這里調(diào)用函數(shù) validate_password 驗(yàn)證字段 password。
在第 3 行到第 6 行,遍歷密碼字段 field 的每個(gè)字符,如果發(fā)現(xiàn)存在一個(gè)非數(shù)字的字符,則正常返回;如果所有的字符都是數(shù)字,則拋出異常 ValidationError。
當(dāng)用戶輸入的密碼全部都是數(shù)字時(shí),表單驗(yàn)證失敗,提示錯(cuò)誤信息為:‘密碼不能全部是數(shù)字’。
5.3 全局驗(yàn)證器
如果想要?jiǎng)?chuàng)建一個(gè)可重用的通用驗(yàn)證器,可以通過定義一個(gè)全局函數(shù)來實(shí)現(xiàn)。
對(duì)前面小節(jié)的例子程序進(jìn)行如下的局部修改,增加一個(gè)全局函數(shù) validate_password,如下:
def can_not_be_all_digits(form, field):
for char in field.data:
print('!', char)
if '0123456789'.find(char) < 0:
return
raise ValidationError('密碼不能全部是數(shù)字')
函數(shù) can_not_be_all_digits 驗(yàn)證字段 field 是否全部是數(shù)字,代碼與上一個(gè)小節(jié)中的函數(shù) validate_password 完全相同。
同時(shí)修改類 LoginForm 的 PasswordField,如下:
class LoginForm(FlaskForm):
password = PasswordField(
label = '密碼',
validators =[
DataRequired(message = '密碼不能為空'),
Length(min = 6, message = '密碼至少包括 6 個(gè)字符'),
can_not_be_all_digits
]
)
在第 7 行,validatros 中增加一個(gè)新的 validator——can_not_be_all_digits。當(dāng)用戶輸入的密碼全部都是數(shù)字時(shí),表單驗(yàn)證失敗,提示錯(cuò)誤信息為:‘密碼不能全部是數(shù)字’。
5.4 源程序下載
6. 小結(jié)
本小節(jié)講解了 Flask 的表單驗(yàn)證的概念和使用,總結(jié)如下:
使用 Flask 的表單驗(yàn)證功能,可以方便的驗(yàn)證表單中的字段的有效性,并渲染輸出表單的 HTML 代碼。