烧瓶蓝图模板文件夹

问题描述 投票:43回答:5

我的烧瓶应用布局是:

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?

python flask
5个回答
52
投票

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

22
投票

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

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

因此,建议使用“覆盖jinja_loader函数”。这就是缺乏文档的地方。修补Flask的加载策略需要一些似乎没有记录的知识,以及对Jinja2的良好理解。

您需要了解两个组件:

  • Jinja2环境
  • Jinja2模板加载器

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

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

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

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

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

所以,我们需要修改DispatchingJinjaLoader的行为。有一段时间,我认为没有好的非沮丧和有效的方法来解决这个问题。但是,显然如果你重写app.jinja_options['loader']本身,我们可以得到我们想要的行为。所以,如果我们将DispatchingJinjaLoader子类化,并修改一个小函数(我想它可能更好地完全重新实现它,但这现在适用),我们有我们想要的行为。总的来说,合理的策略如下(未经测试,但应与现代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做一些copy-pasta。

要将它们组合在一起,您必须在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时,应该使用加载器。


8
投票

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

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

即:

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

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

0
投票

Tks @linqq,你的方法在这里真的很好用,除了我由装饰者做了更好的解决方案。

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

from flask import render_template

您应该像这样导入烧瓶模块:

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%替换为您的实际路径,并确保您的模板文件夹是这样的:Folder Structure

一切都完成了!像往常一样使用render_template函数。

© www.soinside.com 2019 - 2024. All rights reserved.