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