Python Web Flask — 用WTF Form製作表單

Sean Yeh
Python Everywhere -from Beginner to Advanced
15 min readJan 12, 2021
Mount Cook National Park, Canterbury Region, South Island, New Zealand, photo by Sean Yeh

先前我們曾經提到過使用request.form的方式獲得使用者在表單上輸入的資料。

在這裡要介紹大家另外一種在Flask中獲得使用者在表單上輸入的資料的方式,以下就是本篇的主角WTForms。

什麼是WTForms?

WTForms是一個Python的函式庫,它提供了Python開發者在開發網站時,需要的表單呈現以及表單的驗證等功能。WTForms可以與您選擇的網站框架和樣板引擎一起使用,並支援資料的驗證、CSRF的保護、I18N多國化等等功能。

下載與安裝

可以透過pip安裝WTForms套件。

$ pip install WTForms

核心概念

根據官網的說明,以下是使用WTForms的核心概念:

  • Forms表單是WTForms最主要的核心容器。Forms表單是Fields欄位的集合,我們可以選擇透過字典(dictionary)或屬性(attribute)的方式來接觸它們。
  • Fields則將大部分粗重的工作攬在身上。每個Fields欄位都代表某一種資料的類型,且Fields欄位限制使用者僅能夠輸入符合該資料類型的數據。舉例來說IntegerFieldStringField代表的是兩種不一樣的資料型態,一個是數字而另一個則是字串。此外,Fields欄位除包含數據之外,還包含其他有用的屬性,例如label(標籤),description(描述)和錯誤驗證等。
  • 每個Fields欄位都有一個widget實例。widget的工作是呈現該Fields欄位對應的HTML標籤。你可以指定Widget實例給每個特定的Fields欄位,不過在預設的情況下每個Fields欄位都有一個widget實例。
  • 有一些Fields欄位設置的目的只是為了工程師使用上方便,舉例來說TextAreaField只是一個字串欄位(StringField),而預設的widget是TextArea。
  • 為了提供各種驗證規則,Fields欄位包含了一系列的驗證方式。

大致暸解上面的核心概念後,我們就可以將套件匯入專案中。

匯入套件

在專案的python檔中匯入WTForms套件來使用。下面我們匯入了Form表單 、BooleanField(布林值)、StringField(字串)與validators (驗證)等。

from wtforms import Form, BooleanField, StringField, validators

何謂Flask-WTF?

Flask-WTF也是一個Python的函式庫,這個套件可以將您的Flask應用程式與WTForms整合在一起。

由於WTForms不處理檔案的上傳功能:

Does WTForms handle file uploads?

Currently, it does not. This is because WTForms strives to be framework-agnostic, and every web framework handles file uploads somewhat differently. WTForms has a FileField which will let you render a file input widget, but the rest is up to you.

Flask-WTF這個套件包括了檔案上傳、CSRF以及reCAPTCHA驗證等,解決了WTForms無檔案上傳功能的問題。

下載與安裝

我們一樣使用pip來安裝Flask-WTF套件。

$ pip install Flask-WTF

匯入套件

安裝完畢後,就可以在python檔案中匯入Flask-WTF套件來使用。

from flask_wtf import FlaskForm

除了匯入Flask-WTF外,我們還要匯入前面介紹的WTForms。因為Flask-WTF的功能是幫助我們把Flask應用程式與WTForms整合在一起,不過我們只要匯入欄位(例如:StringField, SubmitField),如果需要欄位驗證的話也要匯入validators來驗證欄位。

from wtforms import StringField, SubmitFieldfrom wtforms.validators import DataRequired

註:除了上面的StringField與SubmitField外,你還可以匯入其他各種不一樣的欄位。下面匯入了包含StringField與SubmitField以外的其他欄位:

from wtforms import StringField, BooleanField, DateTimeField, RadioField, SelectField, TextField, TextAreaField, SubmitField

建立表單

匯入套件後,我們就可以開始建立表單了。建立的方式,一樣是建一個類別,不過這個類別繼承的是FlaskForm,而前面的wtforms繼承的是Form。

例如,我們建立了一個名為RegForm的類別,透過這個類別來讓使用者登入。

class RegForm(FlaskForm):    username  = StringField('username',validators=[DataRequired()])    submit = SubmitField("Submit")

在這裡,我們假設有個index首頁,並在該頁中放一個表單,我們可以如下方式建立,其中樣板的名稱為home.html:

# 建立路由@app.route('/', methods=['GET','POST'])
def index():
"""首頁"""
username = False
#form為類別的實體
form = RegForm() if form.validate_on_submit():
#取出username欄位的輸入值
username = form.username.data
#重設username欄位
form.username.data = ''
#將username與form帶入首頁home.html樣板中
return render_template('home.html', form=form,username=username)

在home.html樣板中,可以透過下面方式把表單顯示出來:

<form method="POST">
{{form.hidden_tag()}}
{{form.username.label}} {{form.username()}}
{{form.submit()}}
</form>

上面的程式碼中,有一個 validate_on_submit(),意思是當表單被submit時,才會啟動這個 validate()功能。這個validate功能是form.is_submitted()與form.validate()的縮寫。

WTF Form表單實作

接下來我們要在這裏使用WTF Form來逐步製作一個Flask Form表單。

匯入套件

製作Flask 表單的第一個步驟就是匯入相關的套件。下面我們匯入了所有這個專案需要用到的套件:

from flask import Flask, render_template, session, redirect, url_forfrom flask_wtf import FlaskFormfrom wtforms import (StringField, BooleanField, DateTimeField,RadioField, SelectField, TextField,TextAreaField,SubmitField)from wtforms.validators import DataRequired

上面匯入的套件,除了以前提到過的Flask、render_template、session、redirect、url_for外,還包含了這次討論到的 flask_wtfwtforms。其中我們使用 wtforms 匯入了各種型態的表單欄位,StringField是字串欄位 、BooleanField 布林邏輯判斷欄位、DateTimeField為日期欄位、RadioField 為單選選單、SelectField為下拉選單、TextField文字選單、TextAreaField文字區塊選單以及SubmitField的確認選單。

實體化Flask App

匯入套件後,要實體化Flask,並且加上SECRET KEY的設定,在此我們隨意使用一組SECRET_KEY。

app = Flask(__name__)app.config['SECRET_KEY']='mykey'...略...
if __name__ == '__main__':
app.run(debug=True)

設定表單欄位

實體化Flask後,就可以設定一個表單欄位的Class,在此我們取名為MyForm,這個Class係繼承FlaskForm的類別。

class MyForm(FlaskForm):    ...略...    
pass

建立MyForm類別後,我們就可以在裡面設定欄位:

class MyForm(FlaskForm):
name = StringField('你的名字', validators=[DataRequired()])
agreed = BooleanField('同意加入這個組織?')
gender = RadioField('請輸入性別', choices=[('M','男生'),('F','女生')])
hobby = SelectField('你的興趣', choices=[('sports','運動'),('travel','旅遊'),('movie','電影')])
others= TextAreaField()
submit = SubmitField("確認")

可以看上面的程式碼,在MyForm類別中,我們設定了name為字串欄位(StringField),並且這個欄位設定了檢查的validators屬性。在這個屬性中放置了DataRequired,意指此欄位為必填欄位,在確認時會檢查使用者是否確實輸入這個欄位。

agreed為True或False的BooleanField欄位、gender為單選的Radio Button欄位(RadioFieldchoices為單選欄位的選項hobby為下拉選單(SelectField),choices為下拉選單的選項、others則是文字區塊欄位。

設定路由

接下來要設定表單頁面的路由。在這裏我們只要使用兩個頁面,首頁接收表單資訊,thankyou頁面顯示使用者輸入的資料,由於簡化的關係,這裡使用session來儲存輸入的資料。

#建立首頁

首先建立首頁路由,並寫建立一個index函式。首頁可以接受GET與POST兩種輸入方式。

@app.route('/',methods=['GET','POST'])
def index():
pass

在index函式中,我們透過session的方式將表單個欄位的資料儲存起來。並且導向thankyou的頁面。

def index():
"""首頁"""
form = MyForm()
if form.validate_on_submit():
session['name'] = form.name.data
session['agreed'] = form.agreed.data
session['gender'] = form.gender.data
session['hobby'] = form.hobby.data
session['others'] = form.others.data
return redirect(url_for('thankyou'))
return render_template('home.html', form=form)

至於thank you頁面,則使用templates裡面的 thanktou.html 作為樣板:

@app.route('/thankyou')
def thankyou():
"""thankyou頁"""
return render_template('thankyou.html')

樣板製作

最後,我們需要製作對應的頁面。在templates資料夾裡面,需要建立home.html與thankyou.html兩個樣板。下面是home.html的內容:

{% block content %}<div class="container"><form method="POST"> <!-- form 表單 -->  {{form.hidden_tag()}}  {{form.name.label(class='form-label')}} 
{{form.name(class='form-control')}}
<br>
{{form.agreed.label(class='form-label')}}
{{form.agreed(class='form-control')}}
<br>
{{form.gender.label(class='form-label')}}
{{form.gender()}}
<br>
{{form.hobby.label(class='form-label')}}
{{form.hobby(class='form-control')}}
<br>
{{form.others.label(class='form-label')}}
{{form.others(class='form-control')}}
<br>
{{form.submit(class='btn btn-primary')}}</form>
</div>
{% endblock %}

你可以發現,使用表單非常方便,只要在{{}}裡面加上form.name.label(),就會顯示表單name欄位的label,在裡面加上form.name(),就會顯示name欄位。如果需要調整CSS的話,只要在括弧裡面加上CSS的Class,例如class=’form-label’。

至於填完表單後,路由導向的thankyou.html結果頁面,我們要在這裏把存在session裡面的資料顯示出來。例如{{session[‘name’]}}可以顯示出name的值,{{session[‘agreed’]}}則可以顯示出agreed的值。

{% block content %}<div class="container"><p>感謝填寫,以下是您提供的訊息</p><ul><li>名字: {{session['name']}}</li><li>同意加入這個組織: {{session['agreed']}}</li><li>性別: {{session['gender']}}</li><li>興趣: {{session['hobby']}}</li><li>其他: {{session['others']}}</li></ul></div>{% endblock %}

執行程式

進入首頁(127.0.0.1:5000/)後,應該會看到如下表單。若填寫完畢後,按下確認按鈕,頁面會導向thankyou頁面( 127.0.0.1:5000/thankyou )

首頁

填寫完畢按下「確認」按鈕後的結果:

thankyou頁面

以上是採用WTForms建立Flask Web表單的方法。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。