flask多藍圖模板目錄衝突解決

一、環境

    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:]




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