chapter4 - Web表單

本書使用的是Flask-WTf擴展來處理Web表單,它是把一個獨立的WTForms集成了在Flask程序中。可以使用pip安裝:(venv) $ pip install flask-wtf

4.1 跨站請求偽造保護

默認情況下,Flask-WTF可以保護所有表單免受跨站請求偽造(Cross-Site Request Forgery, CSRF)的攻擊。為了實現這個防護,Flask-WTf需要在程序中設置一個密鑰,然後再使用這個密鑰生成加密令牌,再用令牌驗證請求中表單數據的真偽。設置密鑰的方法如下: hello.py

app = Flask(__name__)app.config[SECRET_KEY] = your secret key

為了安全起見,密鑰是不應該硬編碼到程序中的,而是要保存到環境變數中。到第7章會介紹

4.2 表單類

使用Flask-WTF時,每個web表單都繼續自Form類。這個類中包含了常用HTML表單中的各種欄位,還為這些欄位設置了一些驗證函數可供使用,用於驗證表單的輸入數據。 如下為一個簡單的weg表單,包含了一個文本欄位和一個提交按鈕: hello.py

from flask_wtf import Formfrom wtforms import StringField, SubmitFieldfrom wtforms.validators import Requiredclass NameForm(Form): name = StringField(What is your name?, validators=[Required()]) submit = SubmitField(Submit)

name是一個文本欄位,submit是一個提交按鈕,分別相當於HTML中的:

<input type="text"><input type="submit" value=>

name使用的StringField方法的第一個參數是顯示在頁面中的提示信息,第二個參數是一個表單驗證項,用validators=[]方法實現,該方法接收一個列表,可在列表中放置多個驗證函數。 以下是WTForms支持的HTML標準欄位

欄位類型說明StringField文本欄位TextAreaField多行文本欄位PasswordField密碼文本欄位HiddenField隱藏文本欄位DateField文本欄位,值為datetime.date格式DateTimeField文本欄位,值為datetime.datetime格式IntegerField文本欄位,值為整數DecimalField文本欄位,值為decimal.DecimalFloatField文本欄位,值為浮點數BooleanField複選框,值為True或FalseRadioField一組單選框SelectField下拉列表SelectMultipleField下拉列表,可選擇多個值FileField文件上傳欄位SubmitField表單提交按鈕FormField把表單作為欄位嵌入另一個表單FieldList一組指定類型的欄位

以下是WTForms內建的驗證函數

驗證函數說明Email驗證電子郵件地址EqualTo比較兩個欄位的值;常用於密碼確認IPAddress驗證IPv4網路地址Length驗證輸入字元串的長度NumberRange驗證輸入的值在數字範圍內Optional無輸入值時跳過其他驗證函數Required確保欄位中有數據Regexp使用正則表達式驗證輸入值URL驗證URLAnyOf確保輸入值在可選值列表中NoneOf確保輸入值不在可選值列表中

以下是幾個欄位和驗證函數的使用示例,完整的使用示例和說明可參考末尾處提供的文檔。

  • 密碼錶單欄位的使用:

from wtforms import PasswordFieldpwd1 = PasswordField(請輸入你的密碼:, validators=[Required()])pwd2 = PasswordField(確認密碼:, validators=[Required(), EqualTo(pwd1, message="密碼不匹配")])

首先要導入欄位函數,不過為了方便整個表單的使用,也可以直接使用from wtforms.fields import *的方式導入所有可用表單欄位。PasswordField()方法的第一個參數是提示信息,第二個參數是驗證函數,值是由驗證函數組成的列表,用Required()可以設置必填項,EqualTo()用於匹配兩個表單的輸入,EqualTo()函數的第一個參數是需要進行比對的欄位名稱,第二個參數是匹配失敗的時候的提示信息

  • 單選按鈕的使用

from wtforms import RadioFieldradios = RadioField(sex, choices=[(1, one), (2, two)])

與其它欄位類似,第一個參數是名字,但目前還沒有發現有什麼用處,在前端也不顯示。第二個參數就是單選按鈕的各個選項,可以是元組類型組成的列表,元組的第一個參數是value的值,第二個參數是顯示在頁面前端的名字。如上代碼在前端中是這樣子的:

<div class="radio"> <label> <input id="radio-0" name="radio" value="1" type="radio"> one </label></div><div class="radio"> <label> <input id="radio-1" name="radio" value="2" type="radio"> two </label></div>

對,Jinja2默認地用兩個div來分別顯示這兩個radio了,默認地在頁面前端看到的就是一個在上一個在下

  • 多選框的使用

from wtforms import BooleanFieldcheckbox = BooleanField(one, default=True)

第一個參數是顯示在頁面的名字,第二個參數可設置是否默認選中,如果不設置或者為False,則不選中

4.3 把表單渲染成HTML

表單欄位是可調用的,可以通過參數例如form傳到模板中,然後可以在模板中生成一個簡單的表單:

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

還可以把參數傳入渲染欄位的函數來改進表單的外觀,傳入的參數會被轉換成欄位的HTML屬性。如下代碼可以指定name欄位的id屬性:

<form method="POST"> {{ form.hidden_tag() }} {{ form.name.label }} {{ form.name(id=my-text-field) }} {{ form.submit() }}</form>

當然其實Bootstrap已經為我們提供了一些表單樣式,可以這樣調用:

{% import "bootstrap/wtf.html" as wtf %}{{ wtf.quick_form(form) }}

於是完整的表單代碼如下: 修改 templates/index.html

{% 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 %}

4.4 在視圖函數中處理表單

目前為止,表單在視圖函數中定義了,而模板中也對錶單數據進行了渲染,還沒有把數據發送到模板,因此需要修改視圖函數: 修改 hello.py

@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修飾器中添加了method參數,使得index()視圖函數可以處理GET和POST請求,如果沒有添加的話,則只能處理GET請求,而表單提交通常應使用POST請求。 由於在表單類中為name欄位設置了驗證函數,所以如果驗證通過,則validate_on_submit()方法會返回True,否則返回False。首次載入頁面時,由於表單中沒有數據,因此會返回False,直接載入表單,而在填寫了表單數據並驗證難過後,伺服器會收到一個POST請求,就可以把輸入的數據賦值給name變數並傳到模板中進行渲染。form.name.data = 可以在獲取了表單輸入數據後清空輸入框。 而如果驗證沒通過的話,點擊提交按鈕時會收到一個錯誤提醒。

4.5 重定向和用戶會話

在當前版本的hello.py中,如果在輸入名字後提交表單,再刷新時,會被提示是否要重新發送已填寫數據。在多數情況下,這並不是個理想的處理方式。 使用重定向就可以很好地解決這個問題。Flask中把重定向當成一種特定的響應,其響應的內容是URL。

但還有一個問題,就是當程序處理POST請求結束後,表單中獲取到的用戶輸入數據就丟失了,如果使用重定向,就不能繼續使用這些數據。此時,就要使用到用戶會話,也就是第2章中提到的session。

使用了重定向和用戶會話後的視圖函數如下所示: hello.py

from flask import Flask, render_template, session, redirect, url_for@app.route(/, methods=[GET, POST])def index(): form = NameForm() if form.validate_on_submit(): session[name] = form.name.data return redirect(url_for(index)) return render_template(index.html, form=form, name=session.get(name))

本程序使用了用戶會話session來替代了前面的局部變數name,所以在兩次請求之間也能夠記住輸入的數據。 如上代碼中,如果表單輸入正確,則會調用到redirect()函數。該函數用於生成HTTP重定向響應,其參數是重定向的URL,它的第一個參數必須指定端點名,即路由內部的名字,默認情況下路由端點是相應視圖函數的名字。

4.6 Flash消息

Flash消息用於顯示確認消息、警告或者錯誤提醒。

來自Flask文檔中的Flash消息閃現說明:閃現系統的基本工作方式是:在且只在下一個請求中訪問上一個請求結束時記錄的消息。也就是說,當前看到的Flash消息,是上一次請求結束後設置的。

在hello.py文件中使用Flash消息:

from flask import redirectfrom flask import flashfrom flask import url_for@app.route(/ methods=[GET, POST])def index(): form = NameForm() if form.validate_on_submit(): old_name = session.get(name) if old_name is not None and old_name != form.name.data: flash(Looks like you have changed your name!) session[name] = form.name.data return redirect(url_for(index)) return render_template(index.html, form=form, name=session.get(name))

在示例中,每次提交的名字會跟存儲在session中的上一次提交的名字做比較,如果不一樣,則會調用flash()函數,在發給客戶端的下一個響應中顯示消息。

flash()函數返回的消息需要被模板接收渲染:

修改 templates/base.html

{% block content %}<div class="container"> {% for message in get_flashed_messages() %} <div class="alert alert-warning"> <button type="button" class="close" data-dismiss="alert">&times;</button> {{ message }} </div> {% endfor %} {% block page_content %}{% endblock %}</div>{% endblock %}

在模板中使用循環是因為在之前的請求循環中每次調用flash()函數時,都會生成一個消息,所以可能會有多個消息在排除等待顯示。get_flashed_message()函數獲取的消息在下次調用時不會再次返回,其實這個函數獲取到的消息是一個列表。

WTForms表單的具體使用可參數官方文檔WTForms Documentation

Flash消息閃現的使用可參Flash文檔-消息閃現


推薦閱讀:

我為什麼不贊同使用Python作為啟蒙語言
遲來的第一篇文章。
python 用list of lists表示矩陣的問題?
如果每天堅持用12個小時學習一門編程語言,一年下來,編程能力會達到什麼程度?
python多線程下載,進度條顯示問題如何解決?

TAG:Python | Flask | Python框架 |