藍圖使用起來就像應用當中的子應用一樣,可以有自己的模板,靜態目錄,有自己的視圖函數和URL規則,藍圖之間互相不影響。但是它們又屬於應用中,可以共享應用的配置。對於大型應用來說,我們可以通過添加藍圖來擴展應用功能,而不至於影響原來的程序。不過有一點要注意,目前Flask藍圖的注冊是靜態的,不支持可插拔。
創建一個藍圖
比較好的習慣是將藍圖放在一個單獨的包里,所以讓我們先創建一個”admin”子目錄,並創建一個空的”__init__.py”表示它是一個Python的包。現在我們來編寫藍圖,將其存在”admin/admin_module.py”文件里:
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’指定了這個藍圖的名稱,第二個參數指定了該藍圖所在的模塊名,這里自然是當前文件。
接下來,我們在應用中注冊該藍圖。在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前綴:
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
這樣注冊時就無需指定:
app.register_blueprint(admin_bp)
藍圖資源
藍圖有自己的目錄,它的所有資源都在其目錄下。藍圖的資源目錄是由創建Blueprint對象時傳入的模塊名”__name__”所在的位置決定的。同時,我們可以指定藍圖自己的模板目錄和靜態目錄。比如我們創建藍圖時傳入:
admin_bp = Blueprint('admin', __name__, template_folder='templates', static_folder='static')
這樣,該藍圖的模板目錄就在”admin/templates”下,而靜態目錄就在”admin/static”下。當然,其實默認值就是這兩個位置,不指定也沒關系。我們可以用藍圖對象的”root_path”屬性獲取其主資源路徑,”open_resource()”方法訪問主資源路徑下的某個文件,比如:
# 假設 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時,我們要這樣做:
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)