1. 程式人生 > >Flask 重定向使用者和會話

Flask 重定向使用者和會話

        使用者輸入名字後提交表單,然後點選瀏覽器的重新整理按鈕,會看到一個莫名其妙的警告,要求在再次提交表單之前進行確認。之所以出現這種情況,是因為重新整理頁面時瀏覽器會重新發送之前已經發送過的最後一個請求。如果這個請求是一個包含表單資料的POST 請求,重新整理頁面後會再次提交表單。大多數情況下,這並不是理想的處理方式。很多使用者都不理解瀏覽器發出的這個警告。 基於這個原因,最好別讓Web 程式把 POST 請求作為瀏覽器傳送的最後一個請求。
         這種需求的實現方式是, 使用重定向作為POST 請求的響應,而不是使用常規響應。重定向是一種特殊的響應, 響應內容是URL,而不是包含HTML 程式碼的字串。瀏覽器收到這種響應時, 會向重定向的URL 發起 GET 請求,顯示頁面的內容。這個頁面的載入可能要多花幾微秒, 因為要先把第二個請求發給伺服器。除此之外,使用者不會察覺到有什麼不同。現在,最後一個請求是GET 請求,所以重新整理命令能像預期的那樣正常使用了。這個技巧稱為Post/ 重定向 /Get 模式。
         但這種方法會帶來另一個問題。 程式處理POST 請求時,使用 form.name.data 獲取使用者輸獲取使用者輸入的名字
可是一旦這個請求結束,資料也就丟失了。因為這個 POST 請求使用重定向處理,所以程式需要儲存輸入的名字, 這樣重定向後的請求才能獲得並使用這個名字,從而構建真正的響應。
         程式可以把資料儲存在使用者會話中,在請求之間“ 記住”資料。使用者會話是一種私有儲存,存在於每個連線到伺服器的客戶端中。使用者會話,它是請求上下文中的變數,名為session,像標準的Python 字典一樣操作。

修改 hello.py: 重定向和使用者會話

<span style="font-size:18px;">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'))</span>

區域性變數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。
       此時重新整理瀏覽器頁面,你看到的新頁面就和預期一樣了。
hello.py
from flask import Flask, render_template, session, redirect, url_for
from flask.ext.script import Manager
from flask.ext.bootstrap import Bootstrap
from flask.ext.moment import Moment
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'

manager = Manager(app)
bootstrap = Bootstrap(app)
moment = Moment(app)


class NameForm(Form):
    name = StringField('What is your name?', validators=[Required()])
    submit = SubmitField('Submit')


@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404


@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500


@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'))


if __name__ == '__main__':
    manager.run()

templates\base.html
{% extends "bootstrap/base.html" %}

{% block title %}Flasky{% endblock %}

{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
{% endblock %}

{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">Flasky</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="/">Home</a></li>
            </ul>
        </div>
    </div>
</div>
{% endblock %}

{% block content %}
<div class="container">
    {% block page_content %}{% endblock %}
</div>
{% endblock %}

{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}

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


唯一 URL / 重定向行為

Flask 的 URL 規則基於 Werkzeug 的路由模組。這個模組背後的思想是基於 Apache 以及更早的 HTTP 伺服器主張的先例,保證優雅且唯一的 URL。

以這兩個規則為例:

@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/about')
def about():
    return 'The about page'

雖然它們看起來著實相似,但它們結尾斜線的使用在 URL 定義 中不同。第一種情況中,指向projects 的規範 URL 尾端有一個斜線。這種感覺很像在檔案系統中的資料夾。訪問一個結尾不帶斜線的 URL 會被Flask 重定向到帶斜線的規範 URL 去。

然而,第二種情況的 URL 結尾不帶斜線,類似 UNIX-like 系統下的檔案的路徑名。訪問結尾帶斜線的 URL 會產生一個 404 “Not Found” 錯誤。

這個行為使得在遺忘尾斜線時,允許關聯的 URL 接任工作,與 Apache 和其它的伺服器的行為並無二異。此外,也保證了 URL 的唯一,有助於避免搜尋引擎索引同一個頁面兩次。