FLASK學習系列三-藍圖和視圖函數

藍圖和視圖

視圖函數的作用是響應應用中的請求,flask使用patterns來匹配傳過來的請求url,視圖函數可以返回用於響應的數據,也可以根據視圖名字和參數來生成導向其他視圖的url。

創建一個藍圖
藍圖是組織有聯繫的視圖的一種方式,這些有聯繫的視圖並非直接伴隨着一個應用而生,而是在藍圖中進行登記。當藍圖在工廠函數中可以調用時,便會在應用中對其進行登記。

本項目將會有兩個藍圖,一個藍圖用於用戶的認證,另一個藍圖用於博客發帖。每個藍圖的代碼將會被置於獨立的模塊中。

首先,我們來寫認證的功能。

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”的藍圖,和應用對象一樣,藍圖也必須得知道它是在哪裏定義的,因此__name__便傳遞作爲它的第二個參數,url_prefix()將會返回與藍圖相聯繫的整個url。
從工廠中導入以及註冊藍圖要使用app.register_blueprint(),

def create_app():
    app = ...
    # existing code omitted

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

    return app

認證藍圖將會有視圖註冊新用戶以及登錄和登出。
第一個視圖:登錄

當用戶訪問/auth/register 這個url時,register視圖會返回一個帶有着由其填寫成的表單的HTML頁面(這句不怎麼會翻譯,return HTML with a form for them to fill out.),當表單被提交時,視圖會判斷輸入是否合理。如果不合理的話會返回一個錯誤的信息,合理的話則會創建一個新用戶並會跳轉到登錄的界面。
到目前你只需要寫視圖函數的代碼,在後面的部分將會寫用來生成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.'
        elif db.execute(
            'SELECT id FROM user WHERE username = ?', (username,)
        ).fetchone() is not None:
            error = 'User {} is already registered.'.format(username)

        if error is None:
            db.execute(
                'INSERT INTO user (username, password) VALUES (?, ?)',
                (username, generate_password_hash(password))
            )
            db.commit()
            return redirect(url_for('auth.login'))

        flash(error)

    return render_template('auth/register.html')

註冊函數到底幹了什麼呢?

  1. @bp.route將url(/register) 和register視圖函數連接起來,當flask收到一個發向/auth/register的請求時,它便會調用register視圖函數,並且會使用返回值作爲響應。
  2. 如果用戶提交了表單,request.method將會以POST的方式來傳送數據。在上面的代碼中,我們一開始便驗證方法是否是POST
  3. request.form是一個特殊的字典類型,其描述了所提交的字符串,用戶將會輸入他們的用戶名和密碼。
  4. 並且驗證用戶名和密碼是否爲空
  5. 通過查詢數據庫的中的用戶名來判斷這個用戶名是否被註冊過。數據庫會顧及溢出的值,因此你很難受到sql語句的注入式攻擊。fetchone():會從查詢中返回一行,如果查詢沒有結果,返回爲空。fetchall(): 會返回一個結果的列表。
  6. 如果驗證成功的話,會將新的用戶數據插入到數據庫中。但是爲了安全,密碼不應該直接被存儲到數據庫。存儲密碼之前會用generate_password_hash()將原始密碼變成哈希值,並將這個哈希值存儲。因爲在查詢的過程中會有數據的更改,所以db.commit()需要在之後被調用以便於保存最終的數據。
  7. 在將用戶的數據存儲之後,頁面會被重新定向到login頁面。url_for()會基於登錄視圖的名字來生成轉向登錄界面的url,這要比直接寫url更好,因爲它可以不用更改全部的代碼就可以改變url。redirect()會生成一個跳轉到給定的url的響應。
  8. 如果認證失敗的話錯誤會通過flash來顯現。flash()會存儲一些信息,這些信息可以在渲染模板是被重新的取回。
  9. 當一開始用戶導向/auth/register界面時,實際上會有一個錯誤:一個含有註冊表單的頁面應該會被顯現。render_template()將會渲染一個HTML頁面,這個HTML會在再教程的下下一步被說明。

Login
這個視圖函數的模式和上面一個是相同的。
flaskr/auth.py

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

上面的代碼與註冊時有幾點不同之處。

  1. user信息會先被存儲到一個變量中。
  2. check_password_hash()會以相同的方式散列處哈希值,並與數據庫中同一user的密碼哈希作對比。匹配則說明合理。
  3. session是一個存儲數據的字典
    。當驗證成功時,用戶的id會被存儲到一個新的session中,數據會被存儲到cookie中,這個cookie會被髮送個瀏覽器端,瀏覽器會將cookie以及之後的請求返回。flask會標註數據以免其被幹擾。
    目前用戶的id被存儲到了session中,在之後的請求中它都可以被獲取到。在每個請求的開始,如果用戶已經登錄,他們的信息應該被加載並且可以被其他的視圖使用。
    flaskr/auth.py
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,g.user將不會存在。

logout
要想登出的話,需要將用戶的id從session中移除。移除之後load_logged_in_user在之後的請求中將不會加載用戶數據。
flaskr/auth.py

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

在其他的視圖中獲取認證
創建、編輯、刪除博客都需要用戶登錄。flask中的一個裝飾器對於每個採用此裝飾器的視圖能夠檢查這一點。

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”)

在使用藍圖時,藍圖的名字是先於函數的名字被考慮的,因此對於登錄函數而言你在上面寫的端點的名字是auth.login因爲你將login這個視圖加到了auth這個藍圖中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章