Flask-WTF是簡化了WTForms操作的一個第三方庫。WTForms表單的兩個主要功能是驗證用戶提交數據的合法性以及渲染模板。同時還包含一些其他的功能。例如CSRF保護,文件上傳等功能,安裝flask-wtf也會默認安裝WTForms,通過pip方式安裝:
pip install flask-wtf
表單驗證
1. 自定義一個表單類,繼承自wtform.Form類。
2. 定義好需要驗證的字段,字段的名字必須和模板中input標籤的name字段名字相同
3. 在需要驗證的字段上,需要指定好具體的數據類型
4. 在相關的字段上,指定好驗證器
5. 在視圖中就只需要表單類的對象,進行表單驗證。
from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo
# 表單驗證類
class RegisterForm(Form):
# 導入驗證器
# message爲驗證錯誤時拋出的錯誤信息
username = StringField(validators=[Length(min=3, max=10,
message="用戶名長度必須3到10位")])
password = StringField(validators=[Length(min=6, max=10,
message="密碼長度必須6到10位")])
repeat_password = StringField(validators=[Length(min=6, max=10, message="密碼錯誤"),
EqualTo("password")])
視圖函數的驗證:
@app.route('/register/', methods=["GET", "POST"])
def register():
"""
表單驗證
:return:
"""
if request.method == "GET":
return render_template("html/register.html")
else:
form = RegisterForm(request.form)
if form.validate():
return "success"
else:
print(form.errors) # 獲取具體的錯誤信息
return "Fail"
WTForm常用的驗證器:
Email:驗證上傳的數據是否爲郵箱
EqualTo:驗證上傳的數據是否和另一個字段相等
InputRequired:原始數據的需要驗證,如果不是特殊情況,應該使用InputRequired,即指定這個字段必須是要傳的,否則會報錯
Length:長度限制
NumberRange:數字的區間
Regexp:自定義正則表達式
URL:必須要是url的形式
UUID:驗證uuid
# -*- coding: utf-8 -*-
from wtforms import Form, StringField, IntegerField
from wtforms.validators import Length, EqualTo, InputRequired, Email, NumberRange, URL, UUID, Regexp
# 表單驗證類
class RegisterForm(Form):
# 導入驗證器
# message爲驗證錯誤時拋出的錯誤信息
username = StringField(validators=[Length(min=3, max=10,
message="用戶名長度必須3到10位")])
password = StringField(validators=[Length(min=6, max=10,
message="密碼長度必須6到10位")])
repeat_password = StringField(validators=[Length(min=6, max=10, message="密碼錯誤"),
EqualTo("password")])
# 表單驗證類
class LoginForm(Form):
# email = StringField(validators=[Email(message="郵箱格式錯誤")])
username = StringField(validators=[InputRequired(message="用戶名不能爲空")])
age = IntegerField(validators=[NumberRange(20, 55)])
phone = StringField(validators=[Regexp(r"1[38745]\d{9}")])
自定義驗證器:
當WTForm中的驗證器不能滿足業務需求的時候,用戶可以自定義自己的驗證器:
例如自定義驗證碼的驗證器:
1. 定義一個方法,方法的命名規則是 validate_字段名(self,field)
2. 在方法中使用field.data可以獲取到字段的具體值
3. 如果驗證成功,那麼可以什麼都不做
4. 如果驗證失敗,需要拋出一個異常
# 表單驗證類
class LoginForm(Form):
# email = StringField(validators=[Email(message="郵箱格式錯誤")])
# username = StringField(validators=[InputRequired(message="用戶名不能爲空")])
# age = IntegerField(validators=[NumberRange(20, 55)])
# phone = StringField(validators=[Regexp(r"1[38745]\d{9}")])
captcha = StringField(validators=[Length(4, 4)]) # 首先驗證長度
# 假設驗證碼爲1234, 自定義captcha驗證器,函數名必須以validate開頭
# 所以在驗證captcha的時候,除了驗證Length,還會自動調用下面自定義的函數進行驗證
def validate_captcha(self, field): # 針對具體的字段做驗證
# 通過field.data獲取數據
if field.data == "1234":
return "Success"
else:
raise ValidationError("驗證碼錯誤")
WTForm渲染模板(不推薦使用)
WTForm可以結合jinja2模板渲染表單元素,例如可以渲染各種input, radio等標籤
1. 首先,在form.py文件中定義相關的表單類,定義相關的字段
class SettingForm(Form):
username = StringField("用戶名", validators=[InputRequired()])
age = IntegerField("年齡", validators=[NumberRange(20, 60)])
remember = BooleanField("記住我: ")
tags = SelectField("標籤", choices=[("1", "Java"), ("2", "C++"), ("3", "Android")]
2. 在視圖函數中,將實例化的form對象作爲參數,傳遞到模板文件中:
@app.route('/settings/', methods=["GET", "POST"])
def setting():
if request.method == "GET":
form = SettingForm() # 利用form渲染模板
return render_template("html/settings.html", form=form) # 傳遞參數
else:
form = SettingForm(request.form)
pass
3. 在模板文件中,可以通過變量綁定來使用,同時,可以對相應的字段傳入樣式參數,id, class
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>設置</title>
<style>
.username-input
{
background: blue;
}
#age-input
{
background: pink;
}
</style>
</head>
<body>
<form action="" method="post">
<table>
<tbody>
<tr>
<!--利用form渲染頁面-->
<td>{{ form.username.label }}</td>
{# <td>用戶名</td>#}
{# <td><input type="text" name="username"></td>#}
<td>{{ form.username(class="username-input") }}</td>
</tr>
<tr>
<!--傳入參數-->
<td>{{ form.age.label }}</td>
<td>{{ form.age(id="age-input") }}</td>
</tr>
<tr>
<td>{{ form.remember.label }}</td>
<td>{{ form.remember() }}</td>
</tr>
<tr>
<td>{{ form.tags.label }}</td>
<td>{{ form.tags() }}</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
渲染的效果:
文件上傳
1. 在模板中,需要指定input標籤的enctype才能上傳文件。upload.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上傳</title>
</head>
<body>
<form action="" method="post" enctype="multipart/form-data"> <!--上傳文件要寫enctype-->
<table>
<tbody>
<tr>
<td>頭像: </td>
<td><input type="file" name="avatar"></td>
</tr>
<tr>
<td>描述: </td>
<td><input type="text" name="desc"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
完成上傳界面:
2. 在後臺如果想要獲取上傳的文件,那麼應該使用request.files.get(name)獲取上傳的文件,這裏的name值表示的是對應的input標籤的name屬性的值。
@app.route('/upload/', methods=["GET", "POST"])
def upload():
if request.method == "GET":
return render_template("html/upload.html")
else:
# 接受用戶上傳的數據
desc = request.form.get("desc")
avatar = request.files.get("avatar") # 獲取文件
# 保存文件
file_name = secure_filename(avatar.filename) # 對文件名進行安全檢測
avatar.save("uploaded/" + file_name)
print(desc)
return "文件上傳成功"
3. 獲取到上傳的文件後,可以使用save方法保存文件
【注】: 在保存文件之前,需要使用secure_filename對文件名進行過濾,消除安全隱患:
from werkzeug.utils import secure_filename # 對文件名進行包裝
4. 在服務器上讀取文件,應該定義一個url來獲取指定文件,在視圖函數中使用send_from_directory(文件目錄,文件名)來獲取服務器上的文件:
@app.route('/show/<file_name>/')
def show_image(file_name):
return send_from_directory("uploaded/", filename=file_name)
完整的app.py文件如下:
from flask import Flask, url_for, request, render_template
import config
from exts import db
from forms import RegisterForm, LoginForm, SettingForm
import os
from werkzeug.utils import secure_filename # 對文件名進行包裝
from flask import send_from_directory
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app) # db獲取app中數據庫的連接方式
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/profile/')
def profile():
return "profile page"
@app.route('/register/', methods=["GET", "POST"])
def register():
"""
表單驗證
:return:
"""
if request.method == "GET":
return render_template("html/register.html")
else:
form = RegisterForm(request.form)
if form.validate():
return "success"
else:
print(form.errors) # 獲取具體的錯誤信息
return "Fail"
@app.route('/login/', methods=["GET", "POST"])
def login():
if request.method == "GET":
return render_template("html/login.html")
else:
form = LoginForm(request.form)
if form.validate():
return "success"
else:
print(form.errors)
return "Failed"
@app.route('/settings/', methods=["GET", "POST"])
def setting():
if request.method == "GET":
form = SettingForm() # 利用form渲染模板
return render_template("html/settings.html", form=form)
else:
form = SettingForm(request.form)
pass
@app.route('/upload/', methods=["GET", "POST"])
def upload():
if request.method == "GET":
return render_template("html/upload.html")
else:
# 接受用戶上傳的數據
desc = request.form.get("desc")
avatar = request.files.get("avatar") # 獲取文件
# 保存文件
file_name = secure_filename(avatar.filename) # 對文件名進行安全檢測
avatar.save("uploaded/" + file_name)
print(desc)
return "文件上傳成功"
@app.route('/show/<file_name>/')
def show_image(file_name):
return send_from_directory("uploaded/", filename=file_name)
if __name__ == '__main__':
app.run()
利用Flask_wtf驗證上傳的文件:
1. 對上傳的問價使用表單驗證,在定義表單的時候,對文件的字段需要採用FileField進行驗證
2. 驗證器應該從flask_wtf.file中導入,需要使用的是flask_wtf.file.FileRequired驗證文件是否爲空,flask_wtf.file.FileAllowed
驗證文件的格式是否滿足要求。
3. 在進行驗證的時候,因爲上傳的字符串需要使用request.form進行獲取,上傳的文件需要使用request.file進行獲取,所以在進行驗證的時候,需要對這兩個數據進行組合,這兩個數據都是immutableDict,不可變字典的形式,所以在視圖函數中使用CombineMultiDict將字符串和文件組合在一起,作爲表單驗證的輸入參數。
示例代碼:
form.py中定義的驗證表單
from flask_wtf.file import FileRequired, FileAllowed # 驗證文件必須上傳,以及上傳的類型
# 定義上傳表單驗證
class UploadForm(Form):
avatar = FileField(validators=[FileRequired(), FileAllowed(["jpg", "png", "gif"])])
desc = StringField(validators=[InputRequired()])
視圖函數:
from werkzeug.datastructures import CombinedMultiDict # 將兩個不可變的字典組合到一起
@app.route('/upload/', methods=["GET", "POST"])
def upload():
if request.method == "GET":
return render_template("html/upload.html")
else:
# 接受用戶上傳的數據
# form = UploadForm(request.form) # 因爲這裏字符串是根據request.form獲取的 文件時根據request.form獲取的,需要組合
form = UploadForm(CombinedMultiDict([request.form, request.files]))
if form.validate():
desc = request.form.get("desc")
avatar = request.files.get("avatar") # 獲取文件
# 另一種數據獲取方法
# desc_ = form.desc.data
# avatar_ = form.avatar.data
# 保存文件
file_name = secure_filename(avatar.filename) # 對文件名進行安全檢測
avatar.save("uploaded/" + file_name)
print(desc)
return "文件上傳成功"
else:
print(form.errors)
return "文件上傳失敗"