烧瓶蓝图模板文件夹

时间:2011-11-02 01:13:09

标签: python flask

我的烧瓶应用布局是:

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/仍然显示admin / index.html而不是main / index.html。我检查了app.url_map:

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

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

6 个答案:

答案 0 :(得分:53)

从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')

答案 1 :(得分:22)

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

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

所以,推荐的是一个&#34;覆盖jinja_loader功能&#34;代替。这就是缺乏文档的地方。修补Flask的加载策略需要一些似乎没有记录的知识,以及对Jinja2的良好理解。

您需要了解两个组件:

  • Jinja2环境
  • Jinja2模板加载器

这些是由Flask创建的,具有合理的默认值,自动生成。 (顺便说一下,您可以通过覆盖app.jinja_options来指定自己的Jinja2 options - 但请记住,您将丢失两个Flask默认包含的扩展程序 - autoescapewith - 除非你自己指定它们。看看flask / app.py,看看它们是如何引用它们的。)

环境包含所有这些上下文处理器(例如,您可以在模板中执行var|tojson),辅助函数(url_for等)和变量(g,{{ 1}},session)。它还包含对模板加载器的引用,在这种情况下是上述和自动实例化的app。因此,当您在应用中调用DispatchingJinjaLoader时,它会查找或创建Jinja2环境,设置所有这些好东西,并在其上调用render_template,然后在get_template内调用get_source {1}},尝试稍后描述的一些策略。

如果一切按计划进行,该链将解析查找文件并返回其内容(以及一些other data)。另请注意,这与DispatchingJinjaLoader采用的执行路径相同。

{% extend 'foo.htm' %}做两件事:首先检查应用程序的全局加载程序DispatchingJinjaLoader是否可以找到该文件。如果失败,它会检查{em>所有应用程序蓝图(按照注册顺序,AFAIK)app.jinja_loader以尝试查找文件。跟踪链到最后,这里是jinja_loader的定义(在flask / helpers.py,blueprint.jinja_loader,Flask应用程序和蓝图的基类):

_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)) 是几个标准的Jinja2加载器之一。)但是,这意味着没有真正简单的方法来重新排序蓝图和应用程序范围的模板加载器。

因此,我们需要修改FileSystemLoader的行为。有一段时间,我认为没有好的非沮丧和有效的方法来解决这个问题。但是,显然如果你覆盖DispatchingJinjaLoader本身,我们可以得到我们想要的行为。所以,如果我们继承app.jinja_options['loader'],并修改一个小函数(我想它可能更好地完全重新实现它,但这现在适用),我们有我们想要的行为。总的来说,合理的策略如下(未经测试,但应与现代Flask应用程序一起使用):

DispatchingJinjaLoader

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

要将它们组合在一起,您必须在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

jinja_options

第一次需要模板环境(并因此实例化),这意味着第一次调用render_template时,应该使用加载器。

答案 2 :(得分:9)

两个人的回答很有意思,但另一个问题是,Jinja默认根据其名称缓存模板。因为这两个模板都命名为“index.html”,所以加载器不会为后续蓝图运行。

除了linqq的两个建议外,第三个选项是忽略蓝图的templates_folder选项,并将模板放在应用程序模板目录中的相应文件夹中。

即:

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

答案 3 :(得分:0)

我在fypress和fybb上使用了类似的东西,因为我有一个主题系统。

# utils.templates
from jinja2 import Environment, PackageLoader
from flask.templating import _default_template_ctx_processor
from flask import current_app, url_for, get_flashed_messages


admin_env = Environment(
    loader=PackageLoader('fypress', '/templates/admin/'),
    extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'],
    autoescape=True
)

def render_template(template, **kwargs):
    kwargs.update(_default_template_ctx_processor())
    kwargs.update({
        'url_for': url_for,
        'get_flashed_messages': get_flashed_messages # etc...

    })
    kwargs.update(dict(debug=current_app.config.get('DEBUG'), flask_config=current_app.config))

    template = admin_env.get_template(template)
    return template.render(**kwargs)

然后

# routes.admin.
from flask import Blueprint
from utils.templates import render_template

admin_bp = Blueprint('admin', __name__,  url_prefix='/admin')

@admin_bp.route('/')
def root():
    return render_template('index.html', title='Admin')

答案 4 :(得分:0)

Tks @linqq,您的方法在这里确实很好用,除了装饰员为我提供了更好的解决方案。

请注意,不要像这样导入render_template函数:

from flask import render_template

您应按以下方式导入flask模块:

import flask

然后,将此代码块放在路由器文件的顶部:

def render_decorate(path_prefix):
    def decorate(func):
        def dec_func(*args, **kw):
            arg_list = list(args)
            arg_list[0] = path_prefix + str(arg_list[0])
            arg_tuple = tuple(arg_list)
            return func(*arg_tuple, **kw)
        return dec_func
    return decorate

@render_decorate("%YOUR_DIRECTORY_NAME%/")
def render_template(template_name_or_list, **context):
    return flask.render_template(template_name_or_list, **context)

将%YOUR_DIRECTORY_NAME%替换为您的实际路径,并确保您的template文件夹如下所示: Folder Structure

全部完成!只需照常使用render_template函数即可。

答案 5 :(得分:0)

目前这对我有用。首先在蓝图模板文件夹中搜索模板,如果没有在应用模板文件夹中搜索(用于布局等)。

from jinja2 import BaseLoader, TemplateNotFound
from flask import Flask, current_app, request

class BlueprintLoader(BaseLoader):
    def get_source(self, environment, template):
        for loader in (current_app.blueprints[request.blueprint].jinja_loader, current_app.jinja_loader):
            try:
                if loader:
                    return loader.get_source(environment, template)
            except TemplateNotFound:
                pass
        raise TemplateNotFound(template)

app = Flask(__name__)
app.jinja_env.loader = BlueprintLoader()