Flask Custom jinja2扩展缓存模板在新请求后声明

时间:2017-09-06 12:19:39

标签: flask jinja2

我希望创建一个类似的自定义扩展程序,将任何javascript代码块推送到页面上或页脚下方的指定区域。

我的版本使用Python 3.6,Flask和Jinja 2.9。但是我有一个专业 更改块中的行号或内容后发生的问题。 内容将在渲染时多次出现。

from jinja2 import nodes
from jinja2.ext import Extension

class JavascriptBuilderExtension(Extension):
    tags = set(['push'])

    def __init__(self, environment):
        super(JavascriptBuilderExtension, self).__init__(environment)
        self._myScope = {}
        environment.extend(
            pull = self._myScope
            )
    def parse(self, parser):
        """Parse tokens """
        tag = parser.stream.__next__()
        args = [parser.parse_expression(), nodes.Const(tag.lineno)]
        body = parser.parse_statements(['name:endpush'], drop_needle=True)
        callback = self.call_method('compiled', args)
        return nodes.CallBlock(callback,[], [], body).set_lineno(tag.lineno)

    def compiled(self,tagname,linenum,caller):
        tagname = "{}_{}".format( tagname, linenum)
        self._myScope[tagname] = caller()
        return "<!-- moved {} from line {} -->".format(tagname,linenum)

我的模板代码如下所示

<html> <head></head> <body> <h1>Test template</h1>
{% push 'js' %} X {% endpush %}
{% push 'html' %} Z {% endpush %}
{% push 'js' %} Y {% endpush %}
{{ pull }}
</body> </html>

我的渲染输出如下:

<html> <head></head> <body> <h1>Test template</h1>
name = hyper testing jinja
date = right now
<!-- moved js_4 from line 4 -->
<!-- moved html_5 from line 5 -->
<!-- moved js_6 from line 6 -->
{'js_4': ' X ', 'html_5': ' Z ', 'js_6': ' Y '}
</body> </html>

更改模板块行号或内容后出现问题。

更改内容和行号后

<html> <head></head> <body> <h1>Test template</h1>
{% push 'js' %} ABC {% endpush %}

{% push 'html' %} Z {% endpush %}

{% push 'js' %} 123{% endpush %}
{{ pull }}
</body> </html>

渲染已更改的块现在具有先前内容

<html> <head></head> <body> <h1>Test template</h1>
name = hyper testing jinja
date = right now
<!-- moved js_4 from line 4 -->
<!-- moved html_7 from line 7 -->
<!-- moved js_9 from line 9 -->
{'js_4': ' X ABC', 'html_5': ' Z ', 'js_6': ' Y ','js_9':'123','html_7':'Z'}
</body> </html>

此问题导致将重复内容添加到响应中。

有没有办法在永久页面请求上调用扩展名来重新解析模板以进行新的更改?或者可以不缓存随附的扩展块?

我已经尝试将以下代码添加到自动重新加载模板,但没有帮助解决问题。

app.jinja_env.auto_reload = True

更新:添加了测试代码的链接

Jinja custom extension test code

在进行更改时,调用render_template_string似乎无法正常缓存和呈现。不确定为什么render_template方法缓存。

1 个答案:

答案 0 :(得分:2)

好的,所以这需要花费很多时间来弄清楚,因为有两个问题。

添加扩展程序时,Flask不会重新加载模板

env = app.jinja_env
env.add_extension("flaskext.JavascriptBuilderExtension")

当您添加上述扩展程序时,即使在第一个请求之前,也会创建jinja_env。这内部检查是否设置了TEMPLATES_AUTO_RELOAD,如果未设置,则检查调试值。但截至目前,即使我们的app.run(debug=True)尚未被调用。这就是为什么模板重新加载没有启用

的原因

解决方案是在访问jinja_env

之前手动添加配置
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.config['EXPLAIN_TEMPLATE_LOADING'] = True
env = app.jinja_env
env.add_extension("flaskext.JavascriptBuilderExtension")

使用请求上下文而非全局上下文

接下来,扩展仅初始化一次。所以你使用了下面的代码

def __init__(self, environment):
    super(JavascriptBuilderExtension, self).__init__(environment)
    self._myScope = {}
    environment.extend(
        pull = self._myScope
        )

_myScope变量是在扩展级别创建的,它将保留在那里直到烧瓶运行。因此,即使使用不同的请求,您也可以创建变量并在任何页面呈现中共享数据。需要使用仅在请求之前存在的上下文。为此,可以使用jinja2.nodes.ContextReference

此外,由于数据现在仅在上下文中可用,因此我们需要使用{% pull %}而不是{{ pull }}。我无法从扩展中将变量注入上下文。可能有一种方法,但我的实验失败了。以下是我使用的最后一节课

from jinja2 import nodes
from jinja2.ext import Extension
from jinja2.nodes import ContextReference


class JavascriptBuilderExtension(Extension):
    tags = set(['push','pull'])

    def __init__(self, environment):
        super(JavascriptBuilderExtension, self).__init__(environment)
        self._myScope = {}

    def parse(self, parser):
        raise NotImplementedError()

    def preprocess(self, source, name, filename=None):
        return super(JavascriptBuilderExtension, self).preprocess(source, name, filename)

    def parse(self, parser):
        """Parse tokens """
        tag = parser.stream.__next__()
        ctx_ref = ContextReference()

        if tag.value == "push":
            args = [ctx_ref, parser.parse_expression(), nodes.Const(tag.lineno)]
            body = parser.parse_statements(['name:endpush'], drop_needle=True)
            callback = self.call_method('compiled', args)
        else:
            body = []
            callback = self.call_method('scope', [ctx_ref])

        return nodes.CallBlock(callback, [], [], body).set_lineno(tag.lineno)

    def scope(self, context, caller):
        return str(context.vars["_myScope"])

    def compiled(self, context, tagname, linenum, caller):
        tagname = "{}_{}".format(tagname, linenum)

        if "_myScope" not in context.vars:
            context.vars["_myScope"] = {}

        context.vars["_myScope"][tagname] = caller()

        return "<!-- moved {} from line {} -->".format(tagname, linenum)

主要变化是 - 使用ctx_ref = ContextReference()获取上下文 - 将它传递给回调参数 - 如果不存在,则在上下文中创建_myScope - 为pull

单独渲染

现在当我发出请求时,生成的html是

<html> <head></head> <body> <h1>Test template</h1>
<!-- moved js_2 from line 2 -->

<!-- moved html_4 from line 4 -->

<!-- moved js_6 from line 6 -->

{'js_6': Markup(u' DEF '), 'html_4': Markup(u' Z '), 'js_2': Markup(u' ABC ')}
</body> </html>