配套視頻教程
Flask-WTF 擴展可以把處理 Web 表單的過程變成一種愉悅的體驗。這個擴展對獨立的 WTForms 包進行了包裝,方便集成到 Flask 應用中。
Flask-WTF 及其依賴可使用 pip 安裝:
pip install flask-wtf
配置
與其他多數擴展不同,Flask-WTF 無須在應用層初始化,但是它要求應用配置一個密鑰。密鑰是一個由隨機字符構成的唯一字符串,通過加密或簽名以不同的方式提升應用的安全性。Flask 使用這個密鑰保護用戶會話,以防被篡改。
hello.py
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
app.config
字典可用於存儲 Flask、擴展和應用自身的配置變量。使用標准的字典句法就能把配置添加到 app.config
對象中。這個對象還提供了一些方法,可以從文件或環境中導入配置。
Flask-WTF 之所以要求應用配置一個密鑰,是為了防止表單遭到跨站請求偽造(CSRF,cross-site request forgery)攻擊。惡意網站把請求發送到被攻擊者已登錄的其他網站時,就會引發 CSRF 攻擊。Flask-WTF 為所有表單生成安全令牌,存儲在用戶會話中。
表單類
使用 Flask-WTF 時,在服務器端,每個 Web 表單都由一個繼承自 FlaskForm
的類表示。這個類定義表單中的一組字段,每個字段都用對象表示。字段對象可附屬一個或多個驗證函數。驗證函數用於驗證用戶提交的數據是否有效。
示例是一個簡單的 Web 表單,包含一個文本字段和一個提交按鈕。
hello.py
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class NameForm(FlaskForm):
name = StringField('What is your name?', validators=[DataRequired()])
submit = SubmitField('Submit')
這個表單中的字段都定義為類變量,而各個類變量的值是相應字段類型的對象。在這個示例中,NameForm
表單中有一個名為 name
的文本字段和一個名為 submit
的提交按鈕。StringField
類表示屬性為 type="text"
的 HTML <input>
元素。SubmitField
類表示屬性為 type="submit"
的 HTML <input>
元素。字段構造函數的第一個參數是把表單渲染成 HTML 時使用的標注(label)。
StringField
構造函數中的可選參數 validators
指定一個由驗證函數組成的列表,在接受用戶提交的數據之前驗證數據。驗證函數 DataRequired()
確保提交的字段內容不為空。
FlaskForm
基類由 Flask-WTF 擴展定義,所以要從flask_wtf
中導入。然而,字段和驗證函數卻是直接從 WTForms 包中導入的。
WTForms 支持的 HTML 標准字段如表所示。
表:WTForms支持的HTML標准字段
字段類型 | 說明 |
---|---|
BooleanField |
復選框,值為 True 和 False |
DateField |
文本字段,值為 datetime.date 格式 |
DateTimeField |
文本字段,值為 datetime.datetime 格式 |
DecimalField |
文本字段,值為 decimal.Decimal |
FileField |
文件上傳字段 |
HiddenField |
隱藏的文本字段 |
MultipleFileField |
多文件上傳字段 |
FieldList |
一組指定類型的字段 |
FloatField |
文本字段,值為浮點數 |
FormField |
把一個表單作為字段嵌入另一個表單 |
IntegerField |
文本字段,值為整數 |
PasswordField |
密碼文本字段 |
RadioField |
一組單選按鈕 |
SelectField |
下拉列表 |
SelectMultipleField |
下拉列表,可選擇多個值 |
SubmitField |
表單提交按鈕 |
StringField |
文本字段 |
TextAreaField |
多行文本字段 |
WTForms 內建的驗證函數如表所示。
表:WTForms驗證函數
驗證函數 | 說明 |
---|---|
DataRequired |
確保轉換類型后字段中有數據 |
Email |
驗證電子郵件地址 |
EqualTo |
比較兩個字段的值;常用於要求輸入兩次密碼進行確認的情況 |
InputRequired |
確保轉換類型前字段中有數據 |
IPAddress |
驗證 IPv4 網絡地址 |
Length |
驗證輸入字符串的長度 |
MacAddress |
驗證 MAC 地址 |
NumberRange |
驗證輸入的值在數字范圍之內 |
Optional |
允許字段中沒有輸入,將跳過其他驗證函數 |
Regexp |
使用正則表達式驗證輸入值 |
URL |
驗證 URL |
UUID |
驗證 UUID |
AnyOf |
確保輸入值在一組可能的值中 |
NoneOf |
確保輸入值不在一組可能的值中 |
把表單渲染成HTML
表單字段是可調用的,在模板中調用后會渲染成 HTML。假設視圖函數通過 form
參數把一個 NameForm
實例傳入模板,在模板中可以生成一個簡單的 HTML 表單,如下所示:
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
注意,除了 name
和 submit
字段,這個表單還有個 form.hidden_tag()
元素。這個元素生成一個隱藏的字段,供 Flask-WTF 的 CSRF 防護機制使用。
當然,這種方式渲染出的表單還很簡陋。調用字段時傳入的任何關鍵字參數都將轉換成字段的 HTML 屬性。例如,可以為字段指定 id
或 class
屬性,然后為其定義 CSS 樣式:
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name(id='my-text-field') }}
{{ form.submit() }}
</form>
即便能指定 HTML 屬性,但按照這種方式渲染及美化表單的工作量還是很大,所以在條件允許的情況下,最好使用 Bootstrap 的表單樣式。Flask-Bootstrap 擴展提供了一個高層級的輔助函數,可以使用 Bootstrap 預定義的表單樣式渲染整個 Flask-WTF 表單,而這些操作只需一次調用即可完成。使用 Flask-Bootstrap,上述表單可以用下面的方式渲染:
{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
import
指令的使用方法和普通 Python 代碼一樣,通過它可以導入模板元素,在多個模板中使用。導入的 bootstrap/wtf.html 文件中定義了一個使用 Bootstrap 渲染 Flask-WTF 表單對象的輔助函數。wtf.quick_form()
函數的參數為 Flask-WTF 表單對象,使用 Bootstrap 的默認樣式渲染傳入的表單。hello.py 的完整模板如示例所示。
示例 templates/index.html:使用 Flask-WTF 和 Flask-Bootstrap 渲染表單
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
base.html
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="container">
{% block page_content %}{% endblock %}
</div>
{% endblock %}
在視圖函數中處理表單
在新版 hello.py 中,視圖函數 index()
有兩個任務:一是渲染表單,二是接收用戶在表單中填寫的數據。以下示例是更新后的 index()
視圖函數。
示例 hello.py:使用 GET
和 POST
請求方法處理 Web 表單
@app.route('/', methods=['GET', 'POST'])
def index():
name = None
form = NameForm()
if form.validate_on_submit():
name = form.name.data
form.name.data = ''
return render_template('index.html', form=form, name=name)
app.route
裝飾器中多出的 methods
參數告訴 Flask,在 URL 映射中把這個視圖函數注冊為 GET
和 POST
請求的處理程序。如果沒指定 methods
參數,則只把視圖函數注冊為 GET
請求的處理程序。
這里有必要把 POST
加入方法列表,因為更常使用 POST
請求處理表單提交。表單也可以通過 GET
請求提交,但是 GET
請求沒有主體,提交的數據以查詢字符串的形式附加到 URL 中,在瀏覽器的地址欄中可見。基於這個以及其他多個原因,處理表單提交幾乎都使用 POST
請求。
圖1 是用戶首次訪問網站時瀏覽器顯示的表單。用戶提交名字后,應用會生成一個針對該用戶的歡迎消息。歡迎消息下方還是會顯示這個表單,以便用戶輸入新名字。圖 2 顯示了此時應用的樣子。
圖1:Flask-WTF Web 表單
圖2:提交后顯示的 Web 表單
如果用戶提交表單之前沒有輸入名字,那么 DataRequired()
驗證函數會捕獲這個錯誤,如圖3 所示。注意這個擴展自動提供了多少功能。這說明,像 Flask-WTF 和 Flask-Bootstrap 這樣設計良好的擴展能給應用提供十分強大的功能。
圖3:驗證失敗后顯示的 Web 表單