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

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