概要
在前面章節我們為首頁定義了一個簡單的模板,部分尚未實現的模組如使用者或文章等使用類比的對象作為臨時佔位。
本章我們將看到如何利用web表單填補這些空白。
web表單是web應用中最基本的構建要素,我們將通過表單來實現使用者發帖和應用登入功能。
完成本章內容你需要基於前面章節完成的微博應用代碼,請確認這些代碼已安裝並能正常運行。
配置
Flask-WTF是WTForms項目的Flask架構擴充,我們將用他來協助我們處理web表單。
大部分Flask擴充都需要定義相關配置項,所以我們先來在應用根目錄下建立一個設定檔以備使用。我們先這樣建立 (fileconfig.py):
CSRF_ENABLED = TrueSECRET_KEY = 'you-will-never-guess'
很簡單吧,這是Flask-WTF需要用到的2個配置項。CSRF_ENABLED配置啟用了跨站請求攻擊保護,大部分情況下你都需要開啟此功能,這能使你的應用更安全。
SECRET_KEY設定當CSRF啟用時有效,這將產生一個加密的token供表單驗證使用,你要確保這個KEY足夠複雜不會被簡單推測。
現在這個設定檔已經基本可用了。項目建立完成我們可以建立如下檔案並編輯(fileapp/__init__.py):
from flask import Flask app = Flask(__name__)app.config.from_object('config') from app import views
使用者登入表單
使用Flask-WTF建立的表單就像一個對象,需要從Form類繼承子類。然後在這個子類中定義一些類的屬性變數作為表單欄位就可以了。
我們要建立一個登入表單,用來進行使用者身份識別。但跟平常需要驗證使用者名稱和密碼的登入方式不同,我們將使用 OpenId 來處理登入過程。使用OpenId的好處就是我們不用管那些使用者名稱和密碼的認證過程,交給 OpenId 去搞定,它會返回給我們使用者驗證後的資料。這樣對於使用我們網站的使用者而言也更安全。
使用 OpenId 登入只需要一個字串,然後發送給 OpenId 伺服器就行了。另外我們還需要在表單中加一個“記住我” 的選項框,這個是送給那些不想每次來我們網站都要進行身份認證的人。選擇這個選項後,首次登入時會用cookie在他們的瀏覽器上記住他們的登入資訊,下次再進入網站時就不需要進行登入操作。
開始我們的第一個表單吧 (fileapp/forms.py):
from flask.ext.wtf import Form, TextField, BooleanFieldfrom flask.ext.wtf import Required class LoginForm(Form): openid = TextField('openid', validators = [Required()]) remember_me = BooleanField('remember_me', default = False)
欣賞一下這個類,多麼的簡潔,多麼的一目瞭然。如此簡單,但又十分的富有內涵。我們引入了一個 Form 類,然後繼承這個類,按需求還添加了 TextField 和 BooleanField 這兩個欄位。
另外還引入了一個表單驗證函式 Required,這種驗證函式可以附加在欄位裡面,在使用者提交表單時它們會用來檢查使用者填寫的資料。這個 Required 函數是用來防止使用者提交空資料。Flask-WTF 中還有很多不同作用的表單驗證函式,我們將會在後面使用到它們。
表單範本
現在我們的問題就是需要一個顯示這個登入表單的模板。好訊息是我們剛剛建立的登入表單類知道如何把欄位轉換成HTML,所以我們只需要把注意力集中到頁面配置上。下面就是我們的登入表單的模板 (fileapp/templates/login.html):
{% extends "base.html" %} {% block content %}Sign In
{% endblock %}
容我囉嗦一下,在這個模板中,我們又一次使用了模板繼承的方式。使用 extends 語句從 base.html 繼承模板內容。我們會在後面建立的模板中繼續使用這種方式,這樣可以使我們所有的頁面配置保持一致。
這個登入模板跟普通的HTML表單有些明顯的區別,它使用模板參數 {{ ... }} 來執行個體化表單欄位,而表單欄位又來源於我們剛剛定義的表單類,模板參數中使用了 form 這個名稱。當我們使用視圖函數參考資料表單類並渲染到模板時,我們要特別注意這個把表單類傳遞到模板的變數名。
我們在配置中開啟了CSRF(跨站偽造請求)功能,模板參數 {{ form.hidden_tag() }} 會被替換成一個具有防止CSRF功能的隱藏表單欄位。在開啟了CSRF功能後,所有模板的表單中都需要添加這個模板參數。
我們定義的表單對象中的欄位同樣也能被模板渲染,只需要在模板合適的位置添加類似於 {{ form.field_name }} 這樣的模板參數,相關欄位就會在被定義的位置出現。另外還有一些欄位是可以傳參數,比如這個 openid 欄位,我們就添加了一個參數讓它顯示的寬度增加到80個字元。
由於我們沒有在表單中定義一個提交功能的按鈕,所以在這裡只能以普通表單欄位的方式來做了。不過說起來區區一個按鈕,在表單中跟任何資料都沒有關係,的確也沒有在表單類中定義的必要。
表單視圖
見證奇蹟的時刻最後一步,我們馬上要來寫一個渲染登入表單對象到模板的視圖函數。
這個函數相當的簡單無趣,因為我們只需要把表單對象傳遞給模板就行了。下面就是我們這個視圖函數的全部內容 (fileapp/views.py):
from flask import render_template, flash, redirectfrom app import appfrom forms import LoginForm # index view function suppressed for brevity @app.route('/login', methods = ['GET', 'POST'])def login(): form = LoginForm() return render_template('login.html', title = 'Sign In', form = form)
我們引入登入表單類,然後把它執行個體化到一個變數,最後再把這個變數傳給模板。要渲染表單欄位必須的事情也就這些。
上面的代碼中還引入了兩個新對象: falsh 和 redirect, 這個先甭理它們,稍後才用得上。
另外還做了一件事就是在路由裝飾器中添加一個新方法。讓 Flask 明白我們這個視圖函數支援 GET 和 POST 請求。否則這個視圖函數只會響應 GET 請求。我們需要得到使用者填寫表單後提交的資料,這些資料是從 POST 請求中傳遞過來的。
你可以通過在瀏覽器中測試這個程式來瞭解上面所說的。 按照視圖函數關聯的路由,你應該在瀏覽器中輸入 http://localhost:5000/login
由於我們還沒有寫任何接收資料的代碼,所以現在你在頁面中點提交按鈕還沒有任何效果。
從表單中接收資料
另外值得一提的是, Flask-WTF 對錶單提交資料的處理使我們的接下來要做的事情變得簡單了。下面就是我們這個登入視圖函數的新版本, 加入了表單資料驗證和處理 (fileapp/views.py):
@app.route('/login', methods = ['GET', 'POST'])def login(): form = LoginForm() if form.validate_on_submit(): flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data)) return redirect('/index') return render_template('login.html', title = 'Sign In', form = form)
validate_on_submit() 這個方法做了表單處理的所有工作。如果你在表單向使用者提供資料時(舉個栗子:使用者在它之前修改了一下提交的資料) 時調用此方法,它會返回 False。發生這樣的情況時,你懂的。不懂?就是提交的資料驗證不通過,你要繼續渲染模板。
在提交請求時調用了表單的 validate_on_submit() 方法後,它會從請求中擷取所有提交的資料,然後使用表單欄位中綁定的驗證函式進行資料驗證。在所有的資料都驗證通過時會返回 True. 這就意味著你可以放心的使用這些表單資料了。
只要有一個欄位驗證不通過,它都會返回 False. 這時就需要我們返回資料給使用者,讓他們來糾正一下錯誤資料。接下來我們將會看到在資料驗證失敗時,如何把錯誤訊息顯示給使用者。
當 validate_on_submit() 方法返回 True 的時候,我們的視圖函數又會調用兩個新的函數。它們都是從Flask 中引入的,flash 函數用來在下一個開啟的頁面中顯示定義的訊息。我們現在用它用來做調試。因為我們現在還沒有做使用者登入模組, 所以只需要把使用者提交上來的資料顯示一下就行了。flash 函數非常有用,比如為使用者的一些操作提供訊息反饋。
flash 函數提供的訊息不會自動出現在我們的網站頁面中,所以我們需要做點事情讓它在頁面中顯示出來。為了讓我們所有頁面都能有這項激動人心的功能,所以就把它添加到基礎模板中吧, 下面是更新後的基礎模板 (fileapp/templates/base.html):
{% if title %} {{title}} - microblog {% else %} microblog {% endif %} Microblog: Home {% with messages = get_flashed_messages() %} {% if messages %}
{% for message in messages %}
- {{ message }}
{% endfor %}
{% endif %} {% endwith %} {% block content %}{% endblock %}
模板中顯示 flash 訊息的功能希望你能明白。
在視圖函數中我們使用的另一個新函數就是 redirect. 這個函數會通知使用者的瀏覽器跳轉到指定的地址。在我們的視圖函數中,我們使用它跳轉到了首頁。注意跳轉結束後頁面上還會顯示 flash 函數傳遞的訊息哦。
激動人心的時刻到了,運行我們的程式吧,看看錶單是如何工作的吧。不要填寫表單中的 openid 欄位,看看 Required 這個驗證函式是如何發揮威力,把一切發起空資料的請求阻止在千裡之外。
改善一下欄位驗證
我們程式目前狀況不錯,提交不合要求的資料會被阻止,還會返回表單讓使用者修改,基本滿足我們要求。
但似乎還少點什麼。如果我們在使用者提交資料失敗後給使用者點提示,讓他們知道什麼原因引起的,豈不妙哉!太幸運了,用 Flask-WTF 可以輕鬆解決這個問題。
當表單欄位驗證失敗時, Flask-WTF 會添加一個錯誤訊息到表單對象。這些訊息在模板中也是可以使用的,所以我們只需要在模板中添加一點點東西就OK了。
這個就是我們添加了驗證訊息的登入模板 (fileapp/templates/login.html):
{% extends "base.html" %} {% block content %}Sign In
{% endblock %}
我們僅在 openid 欄位的右邊添加了一個迴圈語句,它會把openid欄位驗證失敗的訊息都顯示出來。不論你的表單有多少欄位,所有表單欄位驗證失敗的錯誤訊息都可以用 form.errors.欄位名 這種方式來使用。這個表單中我們的是 form.errors.openid。為了讓錯誤訊息引起使用者的注意,我們還給訊息添加了顯示紅色的 css 樣式。
處理 OpenID 登入
現實生活中,我們發現有很多人都不知道他們擁有一些公用帳號。一部分大牌的網站或服務商都會為他們的會員提供公用帳號的認證。舉個栗子,如果你有一個 google 帳號,其實你就有了一個公用帳號,類似的還有 Yahoo, AOL, Flickr 等。
為了方便我們的使用者能簡單的使用他們的公用帳號,我們將把這些公用帳號的連結添加到一個列表,這樣使用者就不用自手工輸入了。
我們要把一些提供給使用者的公用帳號服務商定義到一個列表裡面,這個列表就放到設定檔中吧 (fileconfig.py):
CSRF_ENABLED = TrueSECRET_KEY = 'you-will-never-guess' OPENID_PROVIDERS = [ { 'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id' }, { 'name': 'Yahoo', 'url': 'https://me.yahoo.com' }, { 'name': 'AOL', 'url': 'http://openid.aol.com/' }, { 'name': 'Flickr', 'url': 'http://www.flickr.com/' }, { 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }]
接下來就是要在我們的登入視圖函數中使用這個列表了:
@app.route('/login', methods = ['GET', 'POST'])def login(): form = LoginForm() if form.validate_on_submit(): flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data)) return redirect('/index') return render_template('login.html', title = 'Sign In', form = form, providers = app.config['OPENID_PROVIDERS'])
我們從 app.config 中引入了公用帳號服務商的配置列表,然後把它作為一個參數通過 render_template 函數引入到模板。
接下來要做的我想你也猜得到,我們需要在登入模板中把這些服務商連結顯示出來。
{% extends "base.html" %} {% block content %}Sign In
{% endblock %}
這次的模板添加的東西似乎有點多。一些公用帳號需要提供使用者名稱,為瞭解決這個我們用了點 javascript。當使用者點擊相關的公用帳號連結時,需要使用者名稱的公用帳號會提示使用者輸入使用者名稱, javascript 會把使用者名稱處理成可用的公用帳號,最後再插入到 openid 欄位的文字框中。
下面這個是在登入頁面點擊 google 連結後顯示的:
結束語
我們目前在使用者登入表單中取得了一些進展,但使用者登入這個網站還是啥事兒都不能幹。目前我們還一直在登入的介面上小打小鬧,是因為我們還沒有記錄一切的資料庫。
所以從下節開始,我們將會整一個資料庫起來,然後真正完成我們的登入系統。不要走開,請繼續關注。
微博(microblog) 目前的版本源碼從下面地址下載:
下載地址:microblog-0.3.zip