藍圖與視圖

視圖函式是您編寫的程式碼,用於回應對應用程式的請求。Flask 使用模式將傳入的請求 URL 與應處理它的視圖進行匹配。視圖返回 Flask 轉換為傳出回應的資料。Flask 也可以反向操作,根據視圖的名稱和引數產生視圖的 URL。

建立藍圖

Blueprint 是一種組織相關視圖和其他程式碼的方式。視圖和其他程式碼不是直接向應用程式註冊,而是向藍圖註冊。然後,當藍圖在工廠函式中可用時,將藍圖向應用程式註冊。

Flaskr 將有兩個藍圖,一個用於身份驗證功能,另一個用於部落格文章功能。每個藍圖的程式碼將放在一個單獨的模組中。由於部落格需要了解身份驗證,您將首先編寫身份驗證藍圖。

flaskr/auth.py
import functools

from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash

from flaskr.db import get_db

bp = Blueprint('auth', __name__, url_prefix='/auth')

這會建立一個名為 'auth'Blueprint。與應用程式物件一樣,藍圖需要知道它的定義位置,因此將 __name__ 作為第二個引數傳遞。url_prefix 將被添加到與藍圖關聯的所有 URL 的前面。

使用 app.register_blueprint() 從工廠匯入和註冊藍圖。將新程式碼放在工廠函式的末尾,然後再傳回應用程式。

flaskr/__init__.py
def create_app():
    app = ...
    # existing code omitted

    from . import auth
    app.register_blueprint(auth.bp)

    return app

身份驗證藍圖將具有註冊新使用者以及登入和登出的視圖。

第一個視圖:註冊

當使用者訪問 /auth/register URL 時,register 視圖將傳回 HTML,其中包含供他們填寫的表單。當他們提交表單時,它將驗證他們的輸入,並顯示包含錯誤訊息的表單,或建立新使用者並轉到登入頁面。

現在您只需編寫視圖程式碼。在下一頁,您將編寫範本以產生 HTML 表單。

flaskr/auth.py
@bp.route('/register', methods=('GET', 'POST'))
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None

        if not username:
            error = 'Username is required.'
        elif not password:
            error = 'Password is required.'

        if error is None:
            try:
                db.execute(
                    "INSERT INTO user (username, password) VALUES (?, ?)",
                    (username, generate_password_hash(password)),
                )
                db.commit()
            except db.IntegrityError:
                error = f"User {username} is already registered."
            else:
                return redirect(url_for("auth.login"))

        flash(error)

    return render_template('auth/register.html')

以下是 register 視圖函式正在執行的操作

  1. @bp.route 將 URL /registerregister 視圖函式關聯起來。當 Flask 接收到對 /auth/register 的請求時,它將呼叫 register 視圖並使用傳回值作為回應。

  2. 如果使用者提交了表單,request.method 將為 'POST'。在這種情況下,開始驗證輸入。

  3. request.form 是一種特殊的 dict 類型,用於映射提交的表單鍵和值。使用者將輸入他們的 usernamepassword

  4. 驗證 usernamepassword 不為空。

  5. 如果驗證成功,則將新使用者資料插入資料庫。

    • db.execute 接受一個 SQL 查詢,其中包含 ? 佔位符,用於任何使用者輸入,以及一個值組,用於替換佔位符。資料庫函式庫將負責轉義這些值,因此您不會受到SQL 注入攻擊的影響。

    • 為了安全起見,密碼永遠不應直接儲存在資料庫中。相反,generate_password_hash() 用於安全地雜湊密碼,並儲存該雜湊值。由於此查詢會修改資料,因此需要之後呼叫 db.commit() 以儲存變更。

    • 如果使用者名稱已存在,則會發生 sqlite3.IntegrityError,這應作為另一個驗證錯誤顯示給使用者。

  6. 儲存使用者後,他們將被重新導向到登入頁面。url_for() 根據登入視圖的名稱產生其 URL。這比直接編寫 URL 更可取,因為它允許您稍後更改 URL,而無需更改連結到它的所有程式碼。redirect() 產生對產生 URL 的重新導向回應。

  7. 如果驗證失敗,則會向使用者顯示錯誤。flash() 儲存可以在呈現範本時檢索的訊息。

  8. 當使用者最初導航到 auth/register,或存在驗證錯誤時,應顯示包含註冊表單的 HTML 頁面。render_template() 將呈現包含 HTML 的範本,您將在教學的下一步中編寫該範本。

登入

此視圖遵循與上面的 register 視圖相同的模式。

flaskr/auth.py
@bp.route('/login', methods=('GET', 'POST'))
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None
        user = db.execute(
            'SELECT * FROM user WHERE username = ?', (username,)
        ).fetchone()

        if user is None:
            error = 'Incorrect username.'
        elif not check_password_hash(user['password'], password):
            error = 'Incorrect password.'

        if error is None:
            session.clear()
            session['user_id'] = user['id']
            return redirect(url_for('index'))

        flash(error)

    return render_template('auth/login.html')

register 視圖相比,有幾個不同之處

  1. 首先查詢使用者並將其儲存在變數中以供稍後使用。

    fetchone() 從查詢中傳回一行。如果查詢未傳回任何結果,則傳回 None。稍後,將使用 fetchall(),它會傳回所有結果的列表。

  2. check_password_hash() 以與儲存的雜湊相同的方式雜湊提交的密碼,並安全地比較它們。如果它們匹配,則密碼有效。

  3. session 是一個 dict,用於跨請求儲存資料。當驗證成功時,使用者的 id 會儲存在新的 session 中。資料儲存在cookie 中,該 cookie 會傳送到瀏覽器,然後瀏覽器會在後續請求中將其傳回。Flask 安全地簽署資料,使其不會被篡改。

現在使用者的 id 儲存在 session 中,它將在後續請求中可用。在每個請求開始時,如果使用者已登入,則應載入其資訊並使其可供其他視圖使用。

flaskr/auth.py
@bp.before_app_request
def load_logged_in_user():
    user_id = session.get('user_id')

    if user_id is None:
        g.user = None
    else:
        g.user = get_db().execute(
            'SELECT * FROM user WHERE id = ?', (user_id,)
        ).fetchone()

bp.before_app_request() 註冊一個在視圖函式之前執行的函式,無論請求的 URL 是什麼。load_logged_in_user 檢查使用者 ID 是否儲存在 session 中,並從資料庫中取得該使用者的資料,將其儲存在 g.user 上,這會持續到請求結束。如果沒有使用者 ID,或者 ID 不存在,則 g.user 將為 None

登出

要登出,您需要從 session 中移除使用者 ID。然後 load_logged_in_user 將不會在後續請求中載入使用者。

flaskr/auth.py
@bp.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index'))

在其他視圖中要求身份驗證

建立、編輯和刪除部落格文章將需要使用者登入。可以使用裝飾器來為應用它的每個視圖檢查這一點。

flaskr/auth.py
def login_required(view):
    @functools.wraps(view)
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for('auth.login'))

        return view(**kwargs)

    return wrapped_view

此裝飾器傳回一個新的視圖函式,該函式包裝了應用它的原始視圖。新函式檢查是否已載入使用者,否則重新導向到登入頁面。如果已載入使用者,則會呼叫原始視圖並正常繼續。您將在編寫部落格視圖時使用此裝飾器。

端點和 URL

url_for() 函式根據名稱和引數產生視圖的 URL。與視圖關聯的名稱也稱為端點,預設情況下,它與視圖函式的名稱相同。

例如,教學稍早添加到應用程式工廠的 hello() 視圖的名稱為 'hello',可以使用 url_for('hello') 連結到它。如果它接受一個引數(您稍後會看到),則可以使用 url_for('hello', who='World') 連結到它。

當使用藍圖時,藍圖的名稱會添加到函式名稱的前面,因此您上面編寫的 login 函式的端點為 'auth.login',因為您將其添加到 'auth' 藍圖。

繼續閱讀 範本