表單類
一個簡單的 Web 表單,包含一個文本字段和一個提交按鈕中狂。
例如:
hello.py:定義表單類
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('What is your name?', validators=[Required()])
submit = SubmitField('Submit')
StringField類表示屬性為 type="text" 的 <input> 元素扑毡。SubmitField 類表示屬性為 type="submit" 的<input> 元素。字段構(gòu)造函數(shù)的第一個參數(shù)是把表單渲染成 HTML 時使用的標(biāo)號勋又。
StringField 構(gòu)造函數(shù)中的可選參數(shù) validators 指定一個由驗證函數(shù)組成的列表,在接受用戶提交的數(shù)據(jù)之前驗證數(shù)據(jù)换帜。驗證函數(shù) Required() 確保提交的字段不為空。
把表單渲染成HTML
Flask-Bootstrap 提供了一個非常高端的輔助函數(shù),可以使用 Bootstrap 中預(yù)先定義好的表單樣式渲染整個 Flask-WTF 表單,而這些操作只需一次調(diào)用即可完成蹲嚣。使用 Flask-Bootstrap,上述表單可使用下面的方式渲染:
{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
在視圖函數(shù)中處理表單
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)
重定向和用戶會話
最新版的 hello.py 存在一個可用性問題隙畜。用戶輸入名字后提交表單,然后點(diǎn)擊瀏覽器的刷新按鈕,會看到一個莫名其妙的警告,要求在再次提交表單之前進(jìn)行確認(rèn)疲眷。之所以出現(xiàn)這種情況,是因為刷新頁面時瀏覽器會重新發(fā)送之前已經(jīng)發(fā)送過的最后一個請求您朽。如果這個請求是一個包含表單數(shù)據(jù)的 POST 請求,刷新頁面后會再次提交表單。
很多用戶都不理解瀏覽器發(fā)出的這個警告哗总。基于這個原因,最好別讓 Web 程序把 POST 請求作為瀏覽器發(fā)送的最后一個請求蛋哭。
這種需求的實現(xiàn)方式是,使用重定向作為 POST 請求的響應(yīng),而不是使用常規(guī)響應(yīng)涮母。重定向是一種特殊的響應(yīng),響應(yīng)內(nèi)容是 URL,而不是包含 HTML 代碼的字符串躁愿。瀏覽器收到這種響應(yīng)時,會向重定向的 URL 發(fā)起 GET 請求,顯示頁面的內(nèi)容沪蓬。這個頁面的加載可能要多花幾微秒,因為要先把第二個請求發(fā)給服務(wù)器。除此之外,用戶不會察覺到有什么不同∫荼ⅲ現(xiàn)在,最后一個請求是 GET 請求,所以刷新命令能像預(yù)期的那樣正常使用了云挟。這個技巧稱為 Post/ 重定向 /Get 模式。
但這種方法會帶來另一個問題园欣。程序處理 POST 請求時,使用 form.name.data 獲取用戶輸入的名字,可是一旦這個請求結(jié)束,數(shù)據(jù)也就丟失了。因為這個 POST 請求使用重定向處理,所以程序需要保存輸入的名字,這樣重定向后的請求才能獲得并使用這個名字,從而構(gòu)建真正的響應(yīng)狮暑。
程序可以把數(shù)據(jù)存儲在用戶會話中,在請求之間“記住”數(shù)據(jù)辉饱。用戶會話是一種私有存儲,存在于每個連接到服務(wù)器的客戶端中。我們在第 2 章介紹過用戶會話,它是請求上下文中的變量,名為 session,像標(biāo)準(zhǔn)的 Python 字典一樣操作缔逛。
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'))
在程序的前一個版本中,局部變量 name 被用于存儲用戶在表單中輸入的名字姓惑。這個變量現(xiàn)在保存在用戶會話中,即 session['name'],所以在兩次請求之間也能記住輸入的值。
現(xiàn)在,包含合法表單數(shù)據(jù)的請求最后會調(diào)用 redirect() 函數(shù)敦冬。redirect() 是個輔助函數(shù),用來生成 HTTP 重定向響應(yīng)唯沮。redirect() 函數(shù)的參數(shù)是重定向的 URL,這里使用的重定向URL 是程序的根地址,因此重定向響應(yīng)本可以寫得更簡單一些,寫成 redirect('/'),但卻會使用 Flask 提供的 URL 生成函數(shù) url_for()。推薦使用 url_for() 生成 URL,因為這個函數(shù)使用 URL 映射生成 URL,從而保證 URL 和定義的路由兼容,而且修改路由名字后依然可用介蛉。
url_for() 函數(shù)的第一個且唯一必須指定的參數(shù)是端點(diǎn)名,即路由的內(nèi)部名字。默認(rèn)情況下,路由的端點(diǎn)是相應(yīng)視圖函數(shù)的名字践险。在這個示例中,處理根地址的視圖函數(shù)是index(),因此傳給 url_for() 函數(shù)的名字是 index。
最后一處改動位于 render_function() 函數(shù)中,使用 session.get('name') 直接從會話中讀取 name 參數(shù)的值巍虫。和普通的字典一樣,這里使用 get() 獲取字典中鍵對應(yīng)的值以避免未找到鍵的異常情況,因為對于不存在的鍵,get() 會返回默認(rèn)值 None。
Flash消息
一個典型例子是,用戶提交了有一項錯誤的登錄表單后,服務(wù)器發(fā)回的響應(yīng)重新渲染了登錄表單,并在表單上面顯示一個消息,提示用戶用戶名或密碼錯誤贰剥。
hello.py:Flash 消息
from flask import Flask, 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 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'))
僅調(diào)用 flash() 函數(shù)并不能把消息顯示出來,程序使用的模板要渲染這些消息筷频。最好在基模板中渲染 Flash 消息,因為這樣所有頁面都能使用這些消息凛捏。Flask 把 get_flashed_messages() 函數(shù)開放給模板,用來獲取并渲染消息,如示例 4-7 所示。
示例 4-7 templates/base.html:渲染 Flash 消息
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
</div>
{% endblock %}
在模板中使用循環(huán)是因為在之前的請求循環(huán)中每次調(diào)用 flash() 函數(shù)時都會生成一個消息,所以可能有多個消息在排隊等待顯示坯癣。get_flashed_messages() 函數(shù)獲取的消息在下次調(diào)用時不會再次返回,因此 Flash 消息只顯示一次,然后就消失了示罗。