一、環境
Flask==0.10.1
Werkzeug==0.10.4
Jinja2==2.8
二、問題
1、藍圖架構
--app
|-user
|-static
|-templates
|-index.html
|-views.py
|-__init_.py
|-box
|-static
|-templates
|-index.html
|-views.py
|-__init_.py
|-__init__.py
2、問題重現
user和box是兩個藍圖,全部註冊在app項目中
如果在user的views中這樣【render_template('index.html')】渲染index.html,可能會訪問到別的藍圖下的同名模板文件。
三、分析
1、app創建時會從Flask註冊函數中讀取template_folder,如果沒有設置,默認是app/templates,作爲全局jinja_loader
2、render_template函數會首先訪問app的全局jinja_loader,從中讀取模板路徑。
3、訪問不到就會循環訪問所有註冊藍圖的jinja_loader。由於循環訪問的是所有藍圖的無序鍵值字典, 所以恰巧碰到其中一個藍圖下有同名模板文件,便會渲染這個模板文件。
4、附上關鍵代碼(/usr/lib/python2.6/site-packages/flask/templating.py)
def get_source(self, environment, template): for loader, local_name in self._iter_loaders(template): try: return loader.get_source(environment, local_name) except TemplateNotFound: pass raise TemplateNotFound(template) def _iter_loaders(self, template): loader = self.app.jinja_loader if loader is not None: yield loader, template # old style module based loaders in case we are dealing with a # blueprint that is an old style module try: module, local_name = posixpath.normpath(template).split('/', 1) blueprint = self.app.blueprints[module] if blueprint_is_module(blueprint): loader = blueprint.jinja_loader if loader is not None: yield loader, local_name except (ValueError, KeyError): pass for blueprint in itervalues(self.app.blueprints): if blueprint_is_module(blueprint): continue loader = blueprint.jinja_loader if loader is not None: yield loader, template
四、解決辦法
1、官方給出的解決辦法(妥協)
將所有模板文件放在一起,按藍圖名字劃分子目錄。也就是全部都放在app的全局jinja_loader目錄。訪問的時候,使用render_template('blueprint_name/template_file.html')這種方式。參見:http://flask.pocoo.org/docs/0.10/blueprints/#templates
2、其他友人的解決辦法
在創建app時修改全局jinja_loader,改爲ChoiceLoader類型,使每個註冊的藍圖loader都作爲一個字典元素插入到ChoiceLoader中的PrefixLoader中,這樣在訪問時就可以在PrefixLoader中根據傳進的藍圖名查找到當前loader路徑。
作者稱這樣的好處不用修改項目架構。不過這樣也會有個問題:使用渲染函數時需要修改模板字符串,因爲是PrefixLoader,需要添加delimiter標示,比如訪問user下的index.html要這樣render_template('user.index.html');參見:http://fewstreet.com/2015/01/16/flask-blueprint-templates.html
3、github上的commit
這種方法修改了render_template函數,在渲染模板時判斷是否是藍圖以及是否存在藍圖loader。如果存在優先使用藍圖自身的loader。這種方法比較乾脆直接,參見:https://github.com/mitsuhiko/flask/pull/1537/files
4、我的解決方式
參照了github解決方法的思路,我在註冊app時通過before_request修改全局jinja_loader,在收到請求時臨時把當前藍圖的loder路徑添加到全局jinja_loader的searchpath列表中,這樣做的目的:①避免修改項目結構,②避免修改render_template參數,③避免修改框架代碼,不方便遷移部署。
五、我的代碼(app/__init__.py)
def create_app(env='development'): """此處略去若干行""" @app.before_request def before_request(): if request.blueprint is not None: bp = app.blueprints[request.blueprint] if bp.jinja_loader is not None: newsearchpath = bp.jinja_loader.searchpath + app.jinja_loader.searchpath app.jinja_loader.searchpath = newsearchpath #以下爲2016-03-11日更新: #如果訪問非藍圖模塊或藍圖中沒有指定template_folder,默認使用app註冊時指定的全局template_floder. else: app.jinja_loader.searchpath = app.jinja_loader.searchpath[-1:] else: app.jinja_loader.searchpath = app.jinja_loader.searchpath[-1:]