烧瓶蓝图模板文件夹

syn*_*tic 47 python flask

我的烧瓶应用布局是:

myapp/
    run.py
    admin/
        __init__.py
        views.py
        pages/
            index.html
    main/
        __init__.py
        views.py
        pages/
            index.html
Run Code Online (Sandbox Code Playgroud)

_ 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')
Run Code Online (Sandbox Code Playgroud)

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 Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

现在,如果我访问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,
Run Code Online (Sandbox Code Playgroud)

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

lin*_*nqq 55

从Flask 0.8开始,蓝图将指定的template_folder添加到应用程序的搜索路径中,而不是将每个目录视为单独的实体.这意味着如果您有两个具有相同文件名的模板,则在搜索路径中找到的第一个模板是使用的模板.这无疑是令人困惑的,此时记录很少(请参阅此错误).看来你并不是唯一被这种行为搞糊涂的人.

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

我想到了两种选择.

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

  • 那么在创建蓝图时,指定template_folder的目的是什么呢?我原以为 render_template 只会查看该文件夹 (3认同)
  • 有类似的问题。我希望以不同的方式处理这个问题。更改静态文件夹的位置对于提供文件来说效果很好,但如果相同的文件已存在,则模板将被覆盖。 (2认同)
  • @DarkSuniuM 它仍然存在,你不能使用同名模板,真的很糟糕的设计 (2认同)

two*_*ter 23

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

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

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

您需要了解两个组件:

  • Jinja2环境
  • Jinja2模板加载器

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

环境包含了所有这些context处理器(例如,所以你可以做var|tojson一个模板),辅助功能(url_for等)和变量(g,session,app).它还包含对模板加载器的引用,在这种情况下是上述和自动实例化的DispatchingJinjaLoader.所以,当你调用render_template你的应用程序,它寻找或创建的Jinja2环境,设置所有这些东西,并要求get_template就可以了,这反过来又调用get_source内部DispatchingJinjaLoader,它试图后面描述的一些策略.

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

DispatchingJinjaLoader做两件事:首先它检查应用程序的全局加载器,它是否app.jinja_loader可以找到该文件.如果不这样做,它会检查所有应用程序蓝图(按照注册顺序,AFAIK),blueprint.jinja_loader以试图找到该文件.跟踪链到最后,这里是jinja_loader的定义(在flask/helpers.py中,_PackageBoundObjectFlask应用程序和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))
Run Code Online (Sandbox Code Playgroud)

啊! 所以现在我们看到了.显然,两者的名称空间将在相同的目录名称上发生冲突.由于首先调用全局加载器,它总是会赢.(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
Run Code Online (Sandbox Code Playgroud)

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

要将它们组合在一起,您必须设置jinja_optionsFlask对象:

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)
Run Code Online (Sandbox Code Playgroud)

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


小智 11

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

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

即:

myapp/templates/admin/index.html
myapp/templates/main/index.html
Run Code Online (Sandbox Code Playgroud)

  • 这个答案(尽管Twoosters得到了很好的解决)在IMO中最有意义,因为如果你想要实现本地优先级效果,那么你就不会将蓝图级模板用于其预期目的 - 从和/或扩展被特定于应用程序的模板覆盖.更简单通常更好.(在这种情况下,更符合意图的性质). (2认同)