烧瓶蓝图模板文件夹
我的烧瓶应用程序布局是:
myapp/ run.py admin/ __init__.py views.py pages/ index.html main/ __init__.py views.py pages/ index.html
_ init _ .py文件是空的。 admin / views.py的内容是:
from flask import Blueprint, render_template admin = Blueprint('admin', __name__, template_folder='pages') @admin.route('/') def index(): return render_template('index.html')
main / views.py与admin / 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/
/main/仍显示admin / index.html而不是main / index.html。 我检查了app.url_map:
<Rule 'admin' (HEAD, OPTIONS, GET) -> admin.index, <Rule 'main' (HEAD, OPTIONS, GET) -> main.index,
另外,我validation了main / views.py中的索引函数是按预期方式调用的。 如果我将main / index.html重命名为不同的东西,那么它可以工作。 那么,如果不进行重命名,那么如何实现1http://127.0.0.1:5000/main/1显示main / index.html?
从Flask 0.8开始,蓝图将指定的template_folder添加到应用程序的searchpath中,而不是将每个目录视为单独的实体。 这意味着如果您有两个具有相同文件名的模板,则在searchpath中find的第一个模板是使用的模板。 这是无可置疑的,目前还没有很好的logging(见这个错误 )。 看来你并不是唯一一个被这种行为困惑的人。
这种行为的devise原因是,蓝图模板可以很容易地从主应用程序的模板中重写,它们是Flask模板searchpath中的首选模板。
想到两个选项。
- 将每个
index.html
文件重命名为唯一的(例如admin.html
和main.html
)。 - 在每个模板文件夹中,将每个模板放在blueprint文件夹的子目录中,然后使用该子目录调用模板。 例如,您的pipe理模板将是
yourapp/admin/pages/admin/index.html
,然后从蓝图内部render_template('admin/index.html')
。
除了上面的linqq的好build议外,如果需要的话,你也可以重写默认的function。 有几个方法:
可以在子类Flask应用程序中重载create_global_jinja_loader
(它将返回在flask / templating.py中定义的DispatchingJinjaLoader
)。 这不是build议,但会奏效。 不鼓励这样做的原因是DispatchingJinjaLoader
具有足够的灵活性来支持自定义加载器的注入。 如果你把自己的装载机搞定了,它将能够依靠默认的,理智的function。
所以,build议使用“替代jinja_loader
函数”。 这是缺less文档的地方。修补Flask的加载策略需要一些似乎没有logging的知识,以及对Jinja2的理解。
有两个组件需要了解:
- Jinja2环境
- Jinja2模板加载器
这些是由Flask自动创build的,具有合理的默认值。 (你可以通过覆盖app.jinja_options
来指定你自己的Jinja2选项 – 但是要记住,除非你自己指定with
否则你将失去Flask默认包含的两个扩展名 – autoescape
和 – 。看看flask / app.py来看看他们是如何引用这些的。)
环境包含所有这些上下文处理器(例如,您可以在模板中执行var|tojson
),帮助函数( url_for
等)和variables( g
, session
, app
)。 它还包含对模板加载器的引用,在这种情况下是前面提到的和自动实例化的DispatchingJinjaLoader
。 所以当你在应用中调用render_template
时候,它会发现或者创buildJinja2环境,设置所有这些好东西,并且调用get_template
,然后在DispatchingJinjaLoader
调用get_source
,尝试稍后描述的几个策略。
如果一切按计划进行,那么这个链将解决find一个文件,并将返回其内容(和其他一些数据 )。 另请注意,这与{% extend 'foo.htm' %}
采取的执行path相同。
DispatchingJinjaLoader
做两件事情:首先检查应用程序的全局加载器,即app.jinja_loader
可以定位文件。 如果失败,它将检查blueprint.jinja_loader的所有应用程序蓝图(按注册顺序,AFAIK)以尝试find该文件。 追踪该链到最后,这里是jinja_loader的定义(在flask / helpers.py中, _PackageBoundObject
,Flask应用程序和Blueprints的基类):
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和应用程序范围的模板加载器进行重新sorting。
所以我们需要修改DispatchingJinjaLoader
的行为。 有一段时间以来,我认为没有一个好的,不泄气的,有效率的方式去做这件事。 然而,显然如果你重写app.jinja_options['loader']
本身,我们可以得到我们想要的行为。 所以,如果我们inheritance了DispatchingJinjaLoader
,并且修改了一个小函数(我认为完全重新实现它可能会更好,但是现在这个函数可以工作),我们有我们想要的行为。 总的来说,一个合理的策略是以下(未经testing,但应该与现代Flask应用程序一起工作):
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中做一些复制意面。
要将它们结合在一起,必须在Flask对象上设置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时,应该使用加载器。
twooster的答案很有意思,但另一个问题是Jinja默认会根据名字caching一个模板。 因为这两个模板都被命名为“index.html”,所以加载程序将不会为后续的蓝图运行。
除了linqq的两个build议之外,第三个选项是一起忽略blueprint的templates_folder选项,并将这些模板放在应用程序模板目录中的相应文件夹中。
即:
myapp/templates/admin/index.html myapp/templates/main/index.html