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

2021-01-07 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 中使用資料庫。

相關焦點

  • 為何平臺類SEM投放熱衷於使用web表單獲客!
    這種渠道方的帳戶許多都是用的百度埠戶,直接投進web表單獲取頭緒,節省在線客服的人工成本,別的也能夠獲得一些百度的返點。下面就具體共享下為何渠道類SEM投進熱衷於運用web表單獲客?其實直接投進表單有以下幾種優點1、獲取百度返點這個是最要害的,關於渠道投進來說,返點便是渠道的利潤,為了獲取返點,運用埠戶投進是最佳策略,由於經過百度直接開的戶是很少有返點的,只能每月用那點優惠券,太少了。可是埠戶有的是不讓放在線客服和自己域名及品牌的。所以跑表單成了榜首挑選。2、節省人工成本直接跑表單能夠不必在線客服,意向客戶能夠留下聯繫方式等候服務商聯繫。省下的人工成本也是一筆不少的預算。
  • 基於web的自定義表單引擎
    目前市面上的地無代碼表單引擎,可以為企業信息管理人員或軟體開發人員提供簡單、快捷、高效的Web表單設計和製作工具,無需編輯任何程序代碼即可輕鬆的與數據綁定並實現表單信息的儲存和流轉。表單引擎的好處對比傳統的開發方式,每一個系統都通過編寫代碼去實現,比如行政管理、人力資源、資產管理、採購審批等信息管理系統。
  • B端產品中,Web端表單如何設計
    但相比其他幾種標籤樣式來說,需要一定的開發成本。優點:空間佔比小,無需用戶對標籤進行記憶缺點:需要一定的開發成本關於標籤樣式的選擇2. 輸入域輸入域是表單的核心主體,包含了文本框、選擇器、開關、複選框、單選框、步驟條、滑塊、上傳、標籤頁等控制項(按類型分)。選擇適合的控制項樣式,能在一定程度上提高表單填寫的效率。
  • 玩大數據一定用得到的19款 Java 開源 Web 爬蟲
    (2)提取鏈:主要是下載網頁,進行DNS轉換,填寫請求和響應表單。(3)抽取鏈:當提取完成時,抽取感興趣的HTML和JavaScript,通常那裡有新的要抓取的URL。(4)寫鏈:存儲抓取結果,可以在這一步直接做全文索引。Heritrix提供了用ARC格式保存下載結果的ARCWriterProcessor實現。
  • 5大策略,幫你設計一個體驗優秀的Web端表單
    在web端上,表單是一種非常常見的存在。那麼對設計師來說,如何設計出優秀的表單呢?什麼樣的表單會帶來良好的用戶體驗呢?筆者將為大家介紹幾個設計策略,希望對你有所幫助。表單(不是表格),作為最為常見的頁面模塊,是不是都快忽略它的存在了?回想一下你登陸網站填寫信息、購物填寫地址、填寫調查問卷、修改個人中心信息時……都是在和表單發生互動。
  • Go 語言極速 web 框架 IRIS V4.1.1 發布 - OSCHINA - 中文開源...
    Go 語言極速 web 框架 IRIS V4.1.1 發布了,更新如下:4.0.0 -> 4.1.1NEW FEATURE: Basic remote control through
  • Go 語言極速 web 框架 IRIS V4 發布 - OSCHINA - 中文開源技術...
    Golang目前已經發展成為非常廣泛使用的開發語言,如果你開發WEB、後臺服務、API,都可以用到golang.
  • 一步步教你開始使用Python開發Web應用
    那麼你準備使用它來做一些web開發,但在探討細節之前,讓我們從頭開始。  學習Python的基礎  截至目前,Python有兩個版本,2.7.5和3.3是目前Python的穩定版本。你選擇哪個學習並不重要,因為區別非常小——尤其對於初學者而言。
  • 工作後轉行Web前端程式設計師是否可行?年齡大了怎麼辦?
    因此,他們獲得融資後第一件事就是招聘Web前端開發人員,就是要先把前端開發做起來,客戶體驗先做起來,這就是為什麼前端很火,前端工程師工資很高的原因。web前端優秀人才需求大,企業高薪搶聘網際網路+時代,web前端市場需求大。隨著信息產業的迅猛發展,行業人才需求量也在逐年擴大。據國內權威數據統計,未來五年,我國信息化人才總需求量高達1500萬—2000萬人。
  • 很全面很實用 10個不錯的CSS3表單教程
    【IT168 應用】CSS3的出現,為製作更好的網頁表單帶來無數新的可能。在本文中,我收集了10種運用新技術CSS3,製作非常棒的表單的教程。  1.HTML5 和 CSS3表單  HTML5引進了類似諸如滑動條、數字旋轉器、日期選擇自定義這些實用的新表單元素。
  • 《web幻想》幸運抽獎 道具再更新
    據經典網頁遊戲《web幻想》官方最新消息,自仲夏夢幻版5月14日勁爆登場之後,得到了廣大玩家的贊同,官方為了感謝各位玩家對「仲夏夢幻版」的支持與喜愛,幻想運營部研究決定於6月5日將進行全區在線內容的更新,更新之後將新增「夢幻樂園
  • 微信開放JS SDK,這場web巨變意味著什麼?
    前段時間iOS 8發布,Apple給第三方廠商開放了自己的js加速引擎Nitro,以強化iOS設備上HTML5的表現。此時的微信JS SDK上線,不必再像以前那樣擔心無法通過App Store審核。而且事實實際上是反過來的,帶有微信JS SDK的版本其實早已更新到App Store了,只是前幾天才給開發者公布了調用接口。
  • Python教程系列之Flask框架的學習
    Flask框架的特點:1自由、靈活、可擴展性強,第三方庫的選擇面廣,開發時可以結合自己喜歡用的輪子,也能結合最流行的最強大的Python庫;2入門簡單,即便沒有多少web開發經驗,也能很快做出網站;3非常適用於小型網站;4非常適用於開發web服務的API;5開發大型網站無壓力,但代碼架構需要自己設計,開發成本取決於開發者的能力和經驗
  • 關於Web表單設計,需要注意的8個要點
    常見的表單設計背後藏著許多秘密,如何讓用戶快速準確的填寫表單,是本文在思考解決的問題。本文偏理論和實踐結果,實例較少,供大家參考和學習。常見問題:在設計表單時,你是否會有如下疑問或思考:誰會填寫表單?為什麼要填寫?如果表單跨多個網頁,需要告訴人們當前處在哪一頁麼?輸入框標籤應當頂對齊、右對齊還是左對齊?表單中如何使用智能默認選項?
  • 一個即將到來的實時的 Web - AI 人工智慧 - cnBeta.COM
    感謝銳商企業CMS的投遞新聞來源:thenextweb.com實時的 Web 離我們還有多遠?
  • 電信能力開放平臺開放免費WEB簡訊API接口
    近日,中國電信天翼開放平臺宣布最新推出免費WEB簡訊API能力接口,向第三方開發者提供免費WEB簡訊推送的能力;據悉,此能力構建於運營商級優質推送服務通道之上,能夠基於WEB頁面實現免費簡訊的精準下發,且能覆蓋國內全網用戶。
  • 「聖經」提摩太前書第4章——作眾人的榜樣,讓別人看出你的長進
    書寫者在提摩太前書第3章中為提摩太講述了揀選監督和執事的標準。在提摩太前書第4章中,書寫者著重介紹了如何去做一個好執事。今天我要給大家分享的內容就是聖經提摩太前書第4章。提摩太前書第4章書寫者採用了對比加囑咐的手法來讓提摩太明白究竟什麼樣的執事才是好執事。
  • 遊藝《最終幻想web》精靈 養成秘籍
    中央平原敵人精靈寶寶掉落特殊物品:小精靈(可交易,100%掉落)2、得到小精靈後,進入倉庫裡的個人小屋,通過栽培溫室,可以得到餵養小精靈所需要的物品3、初級精靈LV1-LV9養成所需要的食品是:LV1:檸檬、鳳梨LV2:石榴、香蕉LV3:蘋果、可可椰子LV4:
  • 我欲封天web正版今日上線QQ空間 千萬人氣小說改編
    為了讓廣大玩家更加暢快淋漓地尊享《我欲封天web》的各項精彩內容,價值888元的封天大禮包免費派送,人人有份,童叟無欺,一切都是為了您高興!  無論你是小說迷還是純遊戲玩家,每一個人都將在《我欲封天web》中收穫屬於自己的驚喜!   高端技術打造巔峰畫面 極致完美呈現第九山   作為一款H5無端手遊,《我欲封天web》在畫質與畫面的打造上美輪美奐,研發方夢啟科技的美術團隊利用最新技術,打造出高水準的精緻畫面,讓玩家在掌中便能感受到恢弘大氣的第九山海,在縹緲的山澗叢林,盡情體驗孟浩的修仙之路!
  • 《Charlotte's Web》Chapter21、22
    A few strands of her old web still hung in the doorway. Every day Wilbur would stand and look at the torn, empty web, and a lump would come to his throat.