Flask 藍圖(Blueprint)

藍圖使用起來就像應用當中的子應用同樣,能夠有本身的模板,靜態目錄,有本身的視圖函數和URL規則,藍圖之間互相不影響。可是它們又屬於應用中,能夠共享應用的配置。對於大型應用來講,咱們能夠經過添加藍圖來擴展應用功能,而不至於影響原來的程序。不過有一點要注意,目前Flask藍圖的註冊是靜態的,不支持可插拔。css

建立一個藍圖

比較好的習慣是將藍圖放在一個單獨的包裏,因此讓咱們先建立一個」admin」子目錄,並建立一個空的」__init__.py」表示它是一個Python的包。如今咱們來編寫藍圖,將其存在」admin/admin_module.py」文件裏:flask

from flask import Blueprint
 
admin_bp = Blueprint('admin', __name__)
 
@admin_bp.route('/')
def index(name):
    return '<h1>Hello, this is admin blueprint</h1>'

咱們建立了藍圖對象」admin_bp」,它使用起來相似於Flask應用的app對象,好比,它能夠有本身的路由」admin_bp.route()」。初始化Blueprint對象的第一個參數’admin’指定了這個藍圖的名稱,第二個參數指定了該藍圖所在的模塊名,這裏天然是當前文件。app

接下來,咱們在應用中註冊該藍圖。在Flask應用主程序中,使用」app.register_blueprint()」方法便可:函數

from flask import Flask
from admin.admin_module import admin_bp
 
app = Flask(__name__)
app.register_blueprint(admin_bp, url_prefix='/admin')
 
if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

「app.register_blueprint()」方法的」url_prefix」指定了這個藍圖的URL前綴。如今,訪問」http://localhost:5000/admin/」就能夠加載藍圖的index視圖了。優化

你也能夠在建立藍圖對象時指定其URL前綴:this

admin_bp = Blueprint('admin', __name__, url_prefix='/admin')

這樣註冊時就無需指定:url

app.register_blueprint(admin_bp)

藍圖資源

藍圖有本身的目錄,它的全部資源都在其目錄下。藍圖的資源目錄是由建立Blueprint對象時傳入的模塊名」__name__」所在的位置決定的。同時,咱們能夠指定藍圖本身的模板目錄和靜態目錄。好比咱們建立藍圖時傳入:spa

admin_bp = Blueprint('admin', __name__,
                     template_folder='templates',
                     static_folder='static')

這樣,該藍圖的模板目錄就在」admin/templates」下,而靜態目錄就在」admin/static」下。固然,其實默認值就是這兩個位置,不指定也不要緊。咱們能夠用藍圖對象的」root_path」屬性獲取其主資源路徑,」open_resource()」方法訪問主資源路徑下的某個文件,好比:debug

# 假設 current app 在路徑 /home/bjhee/flask-app,
#這個將會返回 /home/bjhee/flask-app/admin
print admin_bp.root_path
# 讀取文件 /home/bjhee/flask-app/admin/files/info.txt
with admin_bp.open_resource('files/info.txt') as f:
    info = f.read()
print info

構建URL

構建URL的方法」url_for()」。其第一個參數咱們稱爲端點(Endpoint),通常指向視圖函數名或資源名。藍圖的端點名稱都要加上藍圖名爲前綴,建立Blueprint對象時的第一個參數是藍圖名字,當咱們經過端點名稱來獲取URL時,咱們要這樣作:code

from flask import url_for
 
url_for('admin.index')                          # return /admin/
url_for('admin.static', filename='style.css')   # return /admin/static/style.css

這樣才能得到’admin’藍圖下視圖或資源的URL地址。若是,」url_for()」函數的調用就在本藍圖下,那藍圖名能夠省略,但必須留下」.」表示當前藍圖:

url_for('.index')
url_for('.static', filename='style.css')

藍圖在國際化中的使用

@app.route('/<lang_code>/')
def index(lang_code):
    g.lang_code = lang_code
    return '<h1>Index of language %s</h1>' % g.lang_code
 
@app.route('/<lang_code>/path')
def path(lang_code):
    g.lang_code = lang_code
    return '<h1>Language base URL is %s</h1>' % url_for('index', lang_code=g.lang_code)

每一個路由都要加」<lang_code>」參數,並且每一個視圖函數都要將這個參數保存在上下文環境變量中以便其餘地方使用,能不能簡化呢?讓咱們建立一個以參數作URL前綴的藍圖吧:

from flask import Blueprint, g, url_for
 
lang_bp = Blueprint('lang', __name__, url_prefix='/<lang_code>')
 
@lang_bp.route('/')
def index():
    return '<h1>Index of language %s</h1>' % g.lang_code
 
@lang_bp.route('/path')
def path():
    return '<h1>Language base URL is %s</h1>' % url_for('.index', lang_code=g.lang_code)

將上面的代碼保存在」lang_module.py」中,而後在應用主程序裏註冊:

from lang_module import lang_bp
 
app.register_blueprint(lang_bp)

這樣作的確省去了每一個路由加」<lang_code>」參數的麻煩,但若是運行了該程序,會發現報錯。由於在視圖中沒有」lang_code」傳進來,因此也沒地方設置這個」g.lang_code」變量。這裏,咱們就要用到URL預處理器了,讓咱們回到藍圖代碼」lang_module.py」,加上下面的函數:

@lang_bp.url_value_preprocessor
def get_lang_code_from_url(endpoint, view_args):
    g.lang_code = view_args.pop('lang_code')

這個」@lang_bp.url_value_preprocessor」裝飾器代表,它所裝飾的函數,會在視圖函數被調用以前,URL路徑被預處理時執行。並且只針對當前藍圖的全部視圖有效。它所傳入的第二個參數,保存了當前請求URL路徑上的全部參數的值。因此,上面的」get_lang_code_from_url()」函數就能夠在URL預處理時,設置」g.lang_code」變量。這樣,視圖函數中就能夠取到」g.lang_code」,而咱們的程序也可以正常運行了。

還有能夠優化的地方。每次調用」url_for()」來構建路徑時,必須給」lang_code」參數賦上值。這個是否也能夠統一處理?咱們再加上一個函數:

from flask import current_app
 
@lang_bp.url_defaults
def add_language_code(endpoint, values):
    if 'lang_code' in values or not g.lang_code:
        return
    if current_app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
        values['lang_code'] = g.lang_code

這個」@lang_bp.url_defaults」裝飾器所裝飾的函數,會在每次調用」url_for()」時執行,也只對當前藍圖內的全部視圖有效。它就能夠在構建URL時,設置url規則上參數的默認值,你只需將參數名及其默認值保存在函數的第二個參數values裏便可。

」current_app.url_map.is_endpoint_expecting()」是用來檢查當前的端點是否必須提供一個」lang_code」的參數值。由於咱們這個藍圖裏的全部端點都包含前綴」<lang_code>」,這種狀況下」is_endpoint_expecting」檢查能夠省去,因此上面的函數能夠簡化爲:

@lang_bp.url_defaults
def add_language_code(endpoint, values):
    values.setdefault('lang_code', g.lang_code)

如今,咱們就能夠將視圖函數」url_for()」簡寫爲:

@lang_bp.route('/path')
def path():
    return '<h1>Language base URL is %s</h1>' % url_for('.index')

最終代碼

from flask import Blueprint, g, url_for
 
lang_bp = Blueprint('lang', __name__, url_prefix='/<lang_code>')
 
@lang_bp.route('/')
def index():
    return '<h1>Index of language %s</h1>' % g.lang_code
 
@lang_bp.route('/path')
def path():
    return '<h1>Language base URL is %s</h1>' % url_for('.index')

@lang_bp.url_value_preprocessor
def get_lang_code_from_url(endpoint, view_args):
    g.lang_code = view_args.pop('lang_code')

# 設置url規則上參數的默認值
@lang_bp.url_defaults
def add_language_code(endpoint, values):
    values.setdefault('lang_code', g.lang_code)
相關文章
相關標籤/搜索