2011-11-02 125 views
36

我的瓶的应用程序布局是:烧瓶蓝图模板文件夹

myapp/ 
    run.py 
    admin/ 
     __init__.py 
     views.py 
     pages/ 
      index.html 
    main/ 
     __init__.py 
     views.py 
     pages/ 
      index.html 

_ 初始化 _ .py文件是空的。 管理员/ views.py内容是:

from flask import Blueprint, render_template 
admin = Blueprint('admin', __name__, template_folder='pages') 

@admin.route('/') 
def index(): 
    return render_template('index.html') 

主/ views.py类似于管理员/ views.py

from flask import Blueprint, render_template 
main = Blueprint('main', __name__, template_folder='pages') 

@main.route('/') 
def index(): 
    return render_template('index.html') 

run.py是:

from flask import Flask 
from admin.views import admin 
from main.views import main 

app = Flask(__name__) 
app.register_blueprint(admin, url_prefix='/admin') 
app.register_blueprint(main, url_prefix='/main') 

print app.url_map 

app.run() 

现在,如果我访问http://127.0.0.1:5000/admin/,它正确显示admin/index.html。 但是,http://127.0.0.1:5000/main/仍显示admin/index.html而不是main/index.html。我查app.url_map:

<Rule 'admin' (HEAD, OPTIONS, GET) -> admin.index, 
<Rule 'main' (HEAD, OPTIONS, GET) -> main.index, 

此外,我验证了主/ views.py该索引功能按预期的方式调用。 如果我将main/index.html重命名为不同的东西,那么它可以工作。所以,如果没有 重命名,那么如何实现1http://127.0.0.1:5000/main/1显示main/index.html?

回答

44

从Flask 0.8开始,蓝图将指定的template_folder添加到应用程序的搜索路径中,而不是将每个目录视为单独的实体。这意味着如果您有两个具有相同文件名的模板,则在搜索路径中找到的第一个模板是使用的模板。这当然是令人困惑的,目前还没有很好的记录(见this bug)。 It seems你并不是唯一一个被这种行为困惑的人。

此行为的设计原因是,蓝图模板可以轻松地从主应用程序的模板中覆盖,这些模板是Flask模板搜索路径中的第一行。

想到两个选项。

  • 重命名每个index.html文件是唯一的(例如admin.htmlmain.html)。
  • 在每个模板文件夹中,将每个 模板放在blueprint文件夹的子目录中,然后使用该子目录调用 模板。例如,您的管理员模板将为yourapp/admin/pages/admin/index.html,然后在 之内调用蓝图render_template('admin/index.html')
+0

有类似的问题。我希望这个处理方式不同。更改静态文件夹的位置对于服务文件来说工作得很好,但如果同一个文件已存在,模板将被覆盖。 –

+0

正如jay chan指出的,最简单的方法是将模板存储在主模板文件夹中,以便将admin.html存储在'myapp/templates/admin/index.html'下。 –

+0

为什么不更改'template_folder'到'“admin/pages”或'“main/pages”'? –

21

除了linqq上面的好建议,如果需要,还可以覆盖默认功能。有几种方法:

可以在subclassed Flask应用程序(它返回flask/templating.py中定义的DispatchingJinjaLoader)中覆盖create_global_jinja_loader。这不被推荐,但会起作用。不鼓励这样做的原因是DispatchingJinjaLoader具有足够的灵活性来支持定制装载机的注入。如果你使用自己的装载器,它将能够依靠默认的,理智的功能。

那么,什么建议是一个“覆盖jinja_loader功能”来代替。这是缺乏文档的地方。修补Flask的加载策略需要一些似乎没有记录的知识,以及对Jinja2的理解。

有你需要了解两个部分:

  • 的Jinja2的环境
  • 的Jinja2的模板加载器

这是由瓶创建,具有合理的默认值,自动。 (您可以指定自己的Jinja2 options,顺便说一下,通过重写app.jinja_options - 但是记住,你将失去两个扩展该瓶包括默认 - autoescapewith - 除非你自己指定他们看看瓶/ app.py,看看他们如何引用这些。)

环境包含了所有这些情况下处理器(例如,所以你可以做一个模板var|tojson),辅助功能(url_for等)和变量(g的, sessionapp)。它还包含对模板加载器的引用,在这种情况下,前述和自动实例化DispatchingJinjaLoader。所以,当你在你的应用程序调用render_template,发现或创建的Jinja2环境,设置所有这些东西,并在其上调用get_template,进而调用get_sourceDispatchingJinjaLoader的,它试图后面描述的一些策略。

如果一切按计划进行,该链将寻找一个文件来解决,并返回其内容(以及一些other data)。另请注意,这与{% extend 'foo.htm' %}需要执行的路径相同。

DispatchingJinjaLoader做了两件事:首先它检查应用程序的全局加载程序,即app.jinja_loader是否可以找到该文件。如果做不到这一点,则在试图找到该文件检查所有应用蓝图(在注册,AFAIK的顺序)为blueprint.jinja_loader。跟踪该链的最末端,这里是jinja_loader的定义(在瓶/ helpers.py,_PackageBoundObject,基类两种瓶应用和蓝图):

def jinja_loader(self): 
    """The Jinja loader for this package bound object. 

    .. versionadded:: 0.5 
    """ 
    if self.template_folder is not None: 
     return FileSystemLoader(os.path.join(self.root_path, 
              self.template_folder)) 

啊!所以现在我们看到了。显然,两者的命名空间将在相同的目录名称上发生冲突。由于全局加载程序首先被调用,它总会赢。 (FileSystemLoader是几个标准Jinja2加载器之一)。但是,这意味着没有真正简单的方法来对Blueprint和应用程序范围的模板加载器进行重新排序。

所以,我们需要修改的DispatchingJinjaLoader行为。一段时间以来,我认为没有一种好的,不泄气的,有效的方法来解决这个问题。但是,显然如果你自己重写app.jinja_options['loader'],我们可以得到我们想要的行为。因此,如果我们继承DispatchingJinjaLoader,并修改一个小函数(我认为完全重新实现它可能会更好,但现在可行),但我们有我们想要的行为。总体而言,一个合理的策略会是以下(未经测试,但应与现代瓶应用工作):

from flask.templating import DispatchingJinjaLoader 
from flask.globals import _request_ctx_stack 

class ModifiedLoader(DispatchingJinjaLoader): 
    def _iter_loaders(self, template): 
     bp = _request_ctx_stack.top.request.blueprint 
     if bp is not None and bp in self.app.blueprints: 
      loader = self.app.blueprints[bp].jinja_loader 
      if loader is not None: 
       yield loader, template 

     loader = self.app.jinja_loader 
     if loader is not None: 
      yield loader, template 

此修改原始装载机的方式有两种策略:尝试从蓝图加载(且仅当前正在执行的蓝图,而不是所有的蓝图),如果失败,只能从应用程序加载。如果你喜欢全蓝图的行为,你可以从flask/templating.py中做一些复制意面。

为了将其结合在一起,你必须设置jinja_options的瓶对象:

app = Flask(__name__) 
# jinja_options is an ImmutableDict, so we have to do this song and dance 
app.jinja_options = Flask.jinja_options.copy() 
app.jinja_options['loader'] = ModifiedLoader(app) 

第一次需要一个模板环境(从而实例化),这意味着在第一时间render_template被调用时,你的装载机应该使用。

+0

这太棒了!谢谢你的提示! –

+0

此方法是否允许模板继承? – Andy

7

twooster的回答很有趣,但另一个问题是,神社默认缓存基于其名称的模板。因为这两个模板都被命名为“index.html”,所以加载程序不会为后续蓝图运行。

除了linqq的两个建议,第三个选择是忽略蓝图的templates_folder选项一起并在其中放置各自的文件夹模板在应用程序的模板目录。

即:

myapp/templates/admin/index.html 
myapp/templates/main/index.html 
+2

这个答案(虽然Twoosters很好的解决了)在这里最有意义的IMO,因为如果你试图达到本地优先级的效果,那么你没有使用蓝图级别的模板来达到他们预期的目的 - 从/或者被应用程序特定的模板覆盖。简单通常更好。 (在这种情况下,更符合意图的性质)。 –

+0

如果我理解正确的答案,您说的神社存在缓存问题,当两个模板被命名为'index.html'但随后建议还有一个名为'index.html'两个模板替代。我错过了什么吗? –

+0

区别似乎是,两个新的index.html文件通过模板文件夹下的路径进行区分,而不是直接在其蓝图的模板文件夹下,如“myapp/admin/templates/index.html”和“ MYAPP /主/模板/ index.html的”。 – Thinkable