「Flask web 開發」第4章 Web表單

2020-12-25 Python學習者

第 4 章 Web表單

第 2 章中介紹的請求對象包含客戶端發出的所有請求信息。其中, request.form 能獲取POST 請求中提交的表單數據。

儘管 Flask 的請求對象提供的信息足夠用於處理 Web 表單,但有些任務很單調,而且要重複操作。比如,生成表單的 HTML 代碼和驗證提交的表單數據。

Flask-WTF(http://pythonhosted.org/Flask-WTF/)擴展可以把處理 Web 表單的過程變成一種愉悅的體驗。這個擴展對獨立的 WTForms(http://wtforms.simplecodes.com)包進行了包裝,方便集成到 Flask 程序中。

Flask-WTF 及其依賴可使用 pip 安裝:

(venv) $ pip install flask-wtf4.1 跨站請求偽造保護

默認情況下, Flask-WTF 能保護所有表單免受跨站請求偽造(Cross-Site Request Forgery,CSRF)的攻擊。惡意網站把請求發送到被攻擊者已登錄的其他網站時就會引發 CSRF 攻擊。為了實現 CSRF 保護,Flask-WTF 需要程序設置一個密鑰。 Flask-WTF 使用這個密鑰生成加密令牌,再用令牌驗證請求中表單數據的真偽。設置密鑰的方法如示例 4-1 所示。

示例 4-1hello.py:設置 Flask-WTF

app =Flask(__name__)app.config['SECRET_KEY']='hard to guess string'app.config 字典可用來存儲框架、擴展和程序本身的配置變量。使用標準的字典句法就能把配置值添加到 app.config 對象中。這個對象還提供了一些方法,可以從文件或環境中導入配置值。

SECRET_KEY 配置變量是通用密鑰,可在 Flask 和多個第三方擴展中使用。如其名所示,加密的強度取決於變量值的機密程度。不同的程序要使用不同的密鑰,而且要保證其他人不知道你所用的字符串。

為了增強安全性,密鑰不應該直接寫入代碼,而要保存在環境變量中。這一技術會在第 7 章介紹。

4.2 表單類

使用 Flask-WTF 時,每個 Web 表單都由一個繼承自 Form 的類表示。這個類定義表單中的一組欄位,每個欄位都用對象表示。欄位對象可附屬一個或多個驗證函數。驗證函數用來驗證用戶提交的輸入值是否符合要求。

示例 4-2 是一個簡單的 Web 表單,包含一個文本欄位和一個提交按鈕。

示例 4-2hello.py:定義表單類

from flask_wtf importFormfrom wtforms importStringField,SubmitFieldfrom wtforms.validators importRequiredclassNameForm(Form): name =StringField('What is your name?', validators=[Required()]) submit =SubmitField('Submit')這個表單中的欄位都定義為類變量,類變量的值是相應欄位類型的對象。在這個示例中, NameForm 表單中有一個名為 name 的文本欄位和一個名為 submit 的提交按鈕。 StringField 類表示屬性為 type="text" 的 <input> 元素。 SubmitField 類表示屬性為 type="submit" 的 <input> 元素。欄位構造函數的第一個參數是把表單渲染成 HTML 時使用的標號。

StringField 構造函數中的可選參數 validators 指定一個由驗證函數組成的列表,在接受用戶提交的數據之前驗證數據。驗證函數 Required() 確保提交的欄位不為空。

Form 基類由 Flask-WTF 擴展定義,所以從 flask_wtf 中導入。欄位和驗證函數卻可以直接從 WTForms 包中導入。

WTForms 支持的 HTML 標準欄位如表 4-1 所示。

表4-1 WTForms 支持的 HTML 標準欄位

WTForms 內建的驗證函數如表 4-2 所示。

表4-2 WTForms驗證函數

4.3 把表單渲染成HTML

表單欄位是可調用的,在模板中調用後會渲染成 HTML。假設視圖函數把一個 NameForm 實例通過參數 form 傳入模板,在模板中可以生成一個簡單的表單,如下所示:

<formmethod="POST"> {{ form.hidden_tag() }} {{ form.name.label }} {{ form.name() }} {{ form.submit() }}</form>當然,這個表單還很簡陋。要想改進表單的外觀,可以把參數傳入渲染欄位的函數,傳入的參數會被轉換成欄位的 HTML 屬性。例如,可以為欄位指定 id 或 class 屬性,然後定義 CSS 樣式:

<formmethod="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 渲染 Falsk-WTF 表單對象的輔助函數。 wtf.quick_form() 函數的參數為 Flask-WTF 表單對象,使用 Bootstrap 的默認樣式渲染傳入的表單。 hello.py 的完整模板如示例 4-3 所示。

示例 4-3templates/index.html:使用 Flask-WTF 和 Flask-Bootstrap 渲染表單

{%extends"base.html"%}{%import"bootstrap/wtf.html"as wtf %}{% block title %}Flasky{% endblock %}{% block page_content %}<div><h1>Hello,{%if name %}{{ name }}{%else%}Stranger{% endif %}!</h1></div>{{ wtf.quick_form(form)}}{% endblock %}模板的內容區現在有兩部分。第一部分是頁面頭部,顯示歡迎消息。這裡用到了一個模板條件語句。Jinja2 中的條件語句格式為 {%if condition %}...{%else%}...{% endif %}。如果條件的計算結果為 True,那麼渲染 if和 else 指令之間的值。如果條件的計算結果為 False,則渲染 else 和 endif 指令之間的值。在這個例子中,如果沒有定義模板變量 name,則會渲染字符串" Hello,Stranger!"。內容區的第二部分使用 wtf.quick_form() 函數渲染 NameForm 對象。

4.4 在視圖函數中處理表單

在新版 hello.py 中,視圖函數 index() 不僅要渲染表單,還要接收表單中的數據。示例 4-4是更新後的 index()視圖函數。

示例 4-4hello.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 修飾器中添加的 methods 參數告訴 Flask 在 URL 映射中把這個視圖函數註冊為 GET 和 POST 請求的處理程序。如果沒指定 methods 參數,就只把視圖函數註冊為 GET 請求的處理程序。

把 POST 加入方法列表很有必要,因為將提交表單作為 POST 請求進行處理更加便利。表單也可作為 GET 請求提交,不過 GET 請求沒有主體,提交的數據以查詢字符串的形式附加到 URL 中,可在瀏覽器的地址欄中看到。基於這個以及其他多個原因,提交表單大都作為 POST 請求進行處理。

局部變量 name 用來存放表單中輸入的有效名字,如果沒有輸入,其值為 None。如上述代碼所示,在視圖函數中創建一個 NameForm 類實例用於表示表單。提交表單後,如果數據能被所有驗證函數接受,那麼 validate_on_submit() 方法的返回值為 True,否則返回 False。這個函數的返回值決定是重新渲染表單還是處理表單提交的數據。

用戶第一次訪問程序時,伺服器會收到一個沒有表單數據的 GET 請求,所以 validate_on_submit() 將返回 False。 if 語句的內容將被跳過,通過渲染模板處理請求,並傳入表單對象和值為 None 的 name 變量作為參數。用戶會看到瀏覽器中顯示了一個表單。

用戶提交表單後,伺服器收到一個包含數據的 POST 請求。 validate_on_submit() 會調用 name 欄位上附屬的 Required() 驗證函數。如果名字不為空,就能通過驗證, validate_on_submit() 返回 True。現在,用戶輸入的名字可通過欄位的 data 屬性獲取。在 if 語句中,把名字賦值給局部變量 name,然後再把 data 屬性設為空字符串,從而清空表單欄位。最後一行調用 render_template() 函數渲染模板,但這一次參數 name 的值為表單中輸入的名字,因此會顯示一個針對該用戶的歡迎消息。

提示:如果你從 GitHub 上克隆了這個程序的 Git 倉庫,那麼可以執行 git checkout 4a 籤出程序的這個版本。

圖 4-1 是用戶首次訪問網站時瀏覽器顯示的表單。用戶提交名字後,程序會生成一個針對該用戶的歡迎消息。歡迎消息下方還是會顯示這個表單,以便用戶輸入新名字。圖 4-2 顯示了此時程序的樣子。

圖 4-1 Flask-WTF Web 表單

如果用戶提交表單之前沒有輸入名字, Required() 驗證函數會捕獲這個錯誤,如圖 4-3 所示。注意一下擴展自動提供了多少功能。這說明像 Flask-WTF 和 Flask-Bootstrap 這樣設計良好的擴展能讓程序具有強大的功能。

圖 4-2 提交後顯示的 Web 表單

圖 4-3 驗證失敗後顯示的 Web 表單4.5 重定向和用戶會話

最新版的 hello.py 存在一個可用性問題。用戶輸入名字後提交表單,然後點擊瀏覽器的刷新按鈕,會看到一個莫名其妙的警告,要求在再次提交表單之前進行確認。之所以出現這種情況,是因為刷新頁面時瀏覽器會重新發送之前已經發送過的最後一個請求。如果這個請求是一個包含表單數據的 POST 請求,刷新頁面後會再次提交表單。大多數情況下,這並不是理想的處理方式。

很多用戶都不理解瀏覽器發出的這個警告。基於這個原因,最好別讓 Web 程序把 POST 請求作為瀏覽器發送的最後一個請求。

這種需求的實現方式是,使用重定向作為 POST 請求的響應,而不是使用常規響應。重定向是一種特殊的響應,響應內容是 URL,而不是包含 HTML 代碼的字符串。瀏覽器收到這種響應時,會向重定向的 URL 發起 GET 請求,顯示頁面的內容。這個頁面的加載可能要多花幾微秒,因為要先把第二個請求發給伺服器。除此之外,用戶不會察覺到有什麼不同。現在,最後一個請求是 GET 請求,所以刷新命令能像預期的那樣正常使用了。這個技巧稱為 Post/ 重定向 /Get 模式。

但這種方法會帶來另一個問題。程序處理 POST 請求時,使用 form.name.data 獲取用戶輸入的名字,可是一旦這個請求結束,數據也就丟失了。因為這個 POST 請求使用重定向處理,所以程序需要保存輸入的名字,這樣重定向後的請求才能獲得並使用這個名字,從而構建真正的響應。

程序可以把數據存儲在用戶會話中,在請求之間「記住」數據。用戶會話是一種私有存儲,存在於每個連接到伺服器的客戶端中。我們在第 2 章介紹過用戶會話,它是請求上下文中的變量,名為 session,像標準的 Python 字典一樣操作。

默認情況下,用戶會話保存在客戶端 cookie 中,使用設置的 SECRET_KEY 進行加密籤名。如果篡改了 cookie 中的內容,籤名就會失效,會話也會隨之失效。

示例 4-5 是 index() 視圖函數的新版本,實現了重定向和用戶會話。

示例 4-5hello.py:重定向和用戶會話

from flask importFlask, render_template, session, redirect, url_for@app.route('/', methods=['GET','POST'])def index(): form =NameForm()if form.validate_on_submit(): session['name']= form.name.datareturn redirect(url_for('index'))return render_template('index.html', form=form, name=session.get('name'))在程序的前一個版本中,局部變量 name 被用於存儲用戶在表單中輸入的名字。這個變量現在保存在用戶會話中,即 session['name'],所以在兩次請求之間也能記住輸入的值。

現在,包含合法表單數據的請求最後會調用 redirect() 函數。 redirect() 是個輔助函數,用來生成 HTTP 重定向響應。 redirect() 函數的參數是重定向的 URL,這裡使用的重定向 URL 是程序的根地址,因此重定向響應本可以寫得更簡單一些,寫成 redirect('/'),但卻會使用 Flask 提供的 URL 生成函數 url_for()。推薦使用 url_for() 生成 URL,因為這個函數使用 URL 映射生成 URL,從而保證 URL 和定義的路由兼容,而且修改路由名字後依然可用。

url_for() 函數的第一個且唯一必須指定的參數是端點名,即路由的內部名字。默認情況下,路由的端點是相應視圖函數的名字。在這個示例中,處理根地址的視圖函數是 index(),因此傳給 url_for() 函數的名字是 index。

最後一處改動位於 render_function() 函數中,使用 session.get('name') 直接從會話中讀取 name 參數的值。和普通的字典一樣,這裡使用 get() 獲取字典中鍵對應的值以避免未找到鍵的異常情況,因為對於不存在的鍵, get() 會返回默認值 None。

提示:如果你從 GitHub 上克隆了這個程序的 Git 倉庫,可以執行 git checkout 4b 籤出程序的這個版本。

使用這個版本的程序時,刷新瀏覽器頁面,你看到的新頁面就和預期一樣了。

4.6 Flash消息

請求完成後,有時需要讓用戶知道狀態發生了變化。這裡可以使用確認消息、警告或者錯誤提醒。一個典型例子是,用戶提交了有一項錯誤的登錄表單後,伺服器發回的響應重新渲染了登錄表單,並在表單上面顯示一個消息,提示用戶用戶名或密碼錯誤。

這種功能是 Flask 的核心特性。如示例 4-6 所示, flash() 函數可實現這種效果。

示例 4-6hello.py:Flash 消息

from flask importFlask, render_template, session, redirect, url_for, flash@app.route('/', methods=['GET','POST'])def index(): form =NameForm()if form.validate_on_submit(): old_name = session.get('name')if old_name isnotNoneand old_name != form.name.data: flash('Looks like you have changed your name!') session['name']= form.name.datareturn redirect(url_for('index'))return render_template('index.html',form = form, name = session.get('name'))在這個示例中,每次提交的名字都會和存儲在用戶會話中的名字進行比較,而會話中存儲的名字是前一次在這個表單中提交的數據。如果兩個名字不一樣,就會調用 flash() 函數,在發給客戶端的下一個響應中顯示一個消息。

僅調用 flash() 函數並不能把消息顯示出來,程序使用的模板要渲染這些消息。最好在基模板中渲染 Flash 消息,因為這樣所有頁面都能使用這些消息。 Flask 把 get_flashed_messages() 函數開放給模板,用來獲取並渲染消息,如示例 4-7 所示。

示例 4-7templates/base.html:渲染 Flash 消息

{% block content %}<div>{%for message in get_flashed_messages()%}<div><button type="button"class="close" data-dismiss="alert">&times;</button>{{ message }}</div>{% endfor %}{% block page_content %}{% endblock %}</div>{% endblock %}在這個示例中,使用 Bootstrap 提供的警報 CSS 樣式渲染警告消息(如圖 4-4 所示)。

圖 4-4 Flash 消息在模板中使用循環是因為在之前的請求循環中每次調用 flash() 函數時都會生成一個消息,所以可能有多個消息在排隊等待顯示。 get_flashed_messages() 函數獲取的消息在下次調用時不會再次返回,因此 Flash 消息只顯示一次,然後就消失了。

提示:如果你從 GitHub 上克隆了這個程序的 Git 倉庫,那麼可以執行 git checkout 4c 籤出程序的這個版本。

從 Web 表單中獲取用戶輸入的數據是大多數程序都需要的功能,把數據保存在永久存儲器中也是一樣。下一章將介紹如何在 Flask 中使用資料庫。

相關焦點

  • 基於《Flask Web開發:基於Python的Web應用開發實戰》最全總結
    4 Web Form表單app.config字典可用來存儲框架、擴展和程序本身的配置變量。使用標準的字典句法就能把配置值添加到app.config對象中。按照第 4 章介紹的「Post/ 重定向 /Get 模式」,Login的 POST 請求最後也做了重定向,不過目標 URL 有兩種可能。
  • 好程式設計師web培訓簡述web前端開發工具有哪些
    好程式設計師web培訓簡述web前端開發工具有哪些,隨著網際網路技術的發展,用戶對於web網頁的依賴性越來越重,這對於web前端開發工程師的要求也在不斷提高。作為開發者來說,開發工作需要依賴很多開發工具的協助,才能更高效的完美的呈現出炫酷的頁面。
  • web前端開發常用工具有哪些
    無論是初學小白亦或是自身前端開發人員,好用的軟體工具可以幫助他們更好的工作。下面為大家介紹一些常用的web前端開發工具:1、BootstrapBootstrap 是快速開發 Web 應用程式的前端工具包。
  • Flask Web 小項目——SQL 建表語句生成器
    在完成了數據源確認,SQL 邏輯驗數等工作後,想到還要為這個 SQL 建 Hive 表、建 MySQL 表、建 Clickhouse 表、建調度任務等一系列繁瑣但又必須完成的工序,就會發現「革命遠未成功」。
  • 「全棧之路」Web前端開發的後端指南
    負載平衡器模型通常分為兩類:第4層(傳輸層)和第7層(應用層)。第4層(傳輸層)::第7層(傳輸層)::負載均衡器主要分為硬體負載均衡和軟體負載均衡兩大類。另外一臺機器(代號B)並不提供線上服務,但會實時的將「主機」的數據同步過來,稱為「備機」。一旦「主機」出了故障,通過人工的方式,手動的將「主機」踢下線,將「備機」改為「主機」來繼續提供服務。這個架構的優缺點都很明顯,優點就是幾乎不需要做什麼開發改造,各類資料庫就支持這種模式,部署維護起來也簡單,並沒有引入額外的系統複雜度和瓶頸。
  • 為何平臺類SEM投放熱衷於使用web表單獲客!
    這種渠道方的帳戶許多都是用的百度埠戶,直接投進web表單獲取頭緒,節省在線客服的人工成本,別的也能夠獲得一些百度的返點。下面就具體共享下為何渠道類SEM投進熱衷於運用web表單獲客?其實直接投進表單有以下幾種優點1、獲取百度返點這個是最要害的,關於渠道投進來說,返點便是渠道的利潤,為了獲取返點,運用埠戶投進是最佳策略,由於經過百度直接開的戶是很少有返點的,只能每月用那點優惠券,太少了。可是埠戶有的是不讓放在線客服和自己域名及品牌的。所以跑表單成了榜首挑選。2、節省人工成本直接跑表單能夠不必在線客服,意向客戶能夠留下聯繫方式等候服務商聯繫。省下的人工成本也是一筆不少的預算。
  • 前端開發新手入門:Web開發工具有哪些?
    好的開發工具不僅可以節約時間,更能節約開發成本。作為Web時代最常用的前端開發語言,Web前端受到各大企業的重視,很多零基礎的人都想學習Web前端開發,下面千鋒廣州小編就來給大家介紹一下常見的Web開發工具。
  • cppweb 1.0.4 發布,基於 C++ 開發的 web 伺服器
    cppweb是一個基於C++開發的 WEB 伺服器,支持C/C++、Python、Java等多語言混合開發WEB應用。
  • 「首席架構師看Web趨勢」JavaScript和Web開發InfoQ趨勢報告
    這個月我們將討論JavaScript和Web開發,因為這個快速變化的行業每年需要進行一次以上的更新。這是我們在2018年第一季度回顧的圖表。這是2018年第四季度的修訂版web開發領域對我們來說總是一個有趣的領域,幾乎每天都有新的JavaScript框架發布。
  • 中慧科技Web前端開發系列教材-《Vue應用程式開發》介紹
    4.2.4創建自定義組件4.3內容分發4.3.1單個插槽4.3.2具名插槽4.3.3作用域插槽4.4動態組件4.5案例——使用組件實現購物車功能4.6本章小結4.7本章習題第5章  Vue.js過濾器和自定義指令
  • 10道題教你使用python Django框架來實現web應用,值得收藏
    升級python版本後,第三方包的版本需要升級。有的第三方包不支持python3。這個時候可以嘗試使用2to3工具對第三包的python原始碼進行轉換,然後使用。這樣存在一定風險。無法轉換的,需要尋找替代包,應用伺服器上對應的代碼需要修改。關於django升級:django1.5開始支持python3。
  • Spring Boot中使用Mockito進行Web測試 - 第339篇
    相關歷史文章(閱讀本文前,您可能需要先看下之前的系列👇)國內最全的Spring Boot系列之三2020上半年發文匯總「值得收藏」Slf4j的前世今生之java日誌框架演化歷史 - 第334篇SpringBoot框架開發的優秀的項目「值得收藏學習」 - 第335從Spring整合第三方框架學習Spring Boot - 第336篇Mock工具之Mockito - 第337篇Spring Boot中使用Mockito - 第338篇
  • 如何利用MyEclipse開發工具開發基於Velocity模板的Web應用
    軟體項目實訓及課程設計指導——如何利用MyEclipse開發工具開發基於Velocity模板的Web應用1、在MyEclipse開發工具中新建一個名稱為WebVelocity的Web項目在名稱為WebVelocity的J2EE Web應用項目中的lib目錄下添加
  • 為什麼2017年Web前端開發工程師薪資越來越高?
    原因二:對前端,普遍存在巨大的誤解,其實前端一點也不簡單大多人都認為前端開發是一個「相對於其他模塊來說更簡單的領域」,在他們心中的前端工程師是這樣工作的:1、把Photoshop、保證後臺連接安全,採取跨地資源共享( CORS )的程序考慮,防止跨站點腳本( XSS)和跨站點請求偽造( CSRF ) ;12、最重要的是,儘管有嚴格的期限、利益相關者的要求,以及設備的限制,無論現在還是將來永遠是「客戶第一」。
  • python web學習路線知識點分享!
    >結構python: 內置庫,爬蟲庫,web框架,資料庫接口等。markdown/sphinx/docstring/readthedoc等(使用vim的話推薦python-mode插件,或者直接用IDE工具pycharm)相關框架(庫):django/flask/tornado/requests/sqlalchemy/unittest/celery等等資料庫:
  • 三大最棒的開源Web開發模板或框架
    隨著網際網路的速度要求越來越快,瀏覽器變得越來越標準化和強大,網站的規模和複雜性也越來越多,即便是有經驗的設計師都會使用具有高級功能的設計工具和代碼編輯器來使開發過程更順利。  如今,很少有程式設計師從頭開始設計web網頁了。大多數程式設計師都使用預製模板設計,自定義適合其內容管理系統的選擇。即使是構建複雜Web應用程式的程式設計師也依賴於模板庫。
  • Flask 0.11 發布, Python 開發的 Web 框架
    Added **kwargs to flask.Test.test_client to support passingadditional keyword arguments to the constructor offlask.Flask.test_client_class.
  • Web-STAR:用VB語言為故事理解系統開發的一個基於web的IDE
    在這個工作中,我們關注的是Web-STAR平臺的設計和開發。這個平臺包括一個基於web的集成開發環境(IDE),為每個用戶提供一個個性化的環境,並提供編寫、執行、調試、使用STAR系統的示例故事以及可視化輸出的工具。此外,IDE還是一個社區平臺的基礎,在這裡人們可以分享故事,評論他人,重複使用其他社區創建的故事。
  • Web前端開發技術的教與學(教學大綱)
    第2章HTML基礎2242第3章格式化文本與段落第4章列表2243第5章超連結與浮動框架第6章圖像與多媒體文件22>44第7章CSS基礎第8章DIV與SPAN2245第9章CSS樣式屬性第10章DIV+CSS頁面布局2246第11章表格第12章表單
  • [原創]web報表開發技術專題一:序號問題
    現覺得想用單篇文章來說明 web 報表開發難免會大而空,落不到實處。因而便想到每篇文章只關注 web 報表開發中一方面,由多篇文章而形成一個 web 報表開發的系列文章,這些 web 報表開發的系列文章無先後次序,主從之分。