新请求后Flask自定义jinja2扩展缓存模板状态

新请求后Flask自定义jinja2扩展缓存模板状态,flask,jinja2,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'])

我希望创建一个类似的自定义扩展,将任何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)
    
来自jinja2导入节点的

从jinja2.ext导入扩展
类JavascriptBuilderExtension(扩展名):
tags=set(['push']))
定义初始化(自身、环境):
super(JavascriptBuilderExtension,self)。\uuu init\uuu(环境)
self.\u myScope={}
环境扩展(
拉力=自身
)
def parse(self,parser):
“”“分析令牌”“”
tag=parser.stream.\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
args=[parser.parse_expression(),nodes.Const(tag.lineno)]
body=parser.parse_语句(['name:endpush'],drop_=True)
callback=self.call\u方法('compiled',args)
return nodes.CallBlock(callback,[],[]body).set_lineno(tag.lineno)
def已编译(self、标记名、linenum、调用者):
tagname=“{}}{}”。格式(标记名,linenum)
self._myScope[标记名]=调用方()
返回“”格式(标记名,linenum)
我的模板代码如下所示

<html> <head></head> <body> <h1>Test template</h1>
{% push 'js' %} X {% endpush %}
{% push 'html' %} Z {% endpush %}
{% push 'js' %} Y {% endpush %}
{{ pull }}
</body> </html>
测试模板
{%push'js%}X{%endpush%}
{%push'html%}Z{%endpush%}
{%push'js'}Y{%endpush%}
{{pull}}
我的渲染输出如下:

<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>
测试模板
name=hyper-testing-jinja
日期=现在
{'js_4':'X','html_5':'Z','js_6':'Y'}
在我更改模板块行号或内容后出现问题。

更改内容和行号后

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

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

{% push 'js' %} 123{% endpush %}
{{ pull }}
</body> </html>
测试模板
{%push'js'}ABC{%endpush%}
{%push'html%}Z{%endpush%}
{%push'js'}123{%endpush%}
{{pull}}
渲染更改的块现在具有以前的内容

<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>
测试模板
name=hyper-testing-jinja
日期=现在
{'js_4':'X ABC','html_5':'Z','js_6':'Y','js_9':'123','html_7':'Z'}
此问题导致在响应中添加重复内容

有没有一种方法可以在每次页面请求时调用扩展来重新解析模板以进行新的更改?或者可能不缓存封闭的扩展块

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

app.jinja\u env.auto\u reload=True

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


在进行更改时,调用
render\u template\u string
似乎不会正确缓存和渲染。不确定为什么渲染模板会缓存。

好的,所以这需要花很多时间来解决,因为有两个问题

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

env = app.jinja_env
env.add_extension("flaskext.JavascriptBuilderExtension")
当您像上面那样添加扩展时,甚至在第一个请求之前就会创建
jinja_env
。这将在内部检查是否设置了
模板\u自动\u重新加载
,如果未设置,则检查调试值。但是到目前为止,甚至我们的
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")
使用请求上下文而不是全局上下文

<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>
接下来,扩展只初始化一次。所以您使用了下面的代码

def __init__(self, environment):
    super(JavascriptBuilderExtension, self).__init__(environment)
    self._myScope = {}
    environment.extend(
        pull = self._myScope
        )
\u myScope
变量是在扩展级别创建的,在flask运行之前,该变量将一直保留在扩展级别。因此,您正在创建一个变量,并在任何页面呈现中共享数据,即使有不同的请求。需要使用的上下文只有在请求完成之前是活动的。为此,可以使用
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)

好吧,这花了很多时间才弄清楚,因为有两个问题

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

env = app.jinja_env
env.add_extension("flaskext.JavascriptBuilderExtension")
当您像上面那样添加扩展时,甚至在第一个请求之前就会创建
jinja_env
。这将在内部检查是否设置了
模板\u自动\u重新加载
,如果未设置,则检查调试值。但是到目前为止,甚至我们的
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")
使用请求上下文而不是全局上下文

<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>
接下来,扩展只初始化一次。所以您使用了下面的代码

def __init__(self, environment):
    super(JavascriptBuilderExtension, self).__init__(environment)
    self._myScope = {}
    environment.extend(
        pull = self._myScope
        )
\u myScope
变量是在扩展级别创建的,在flask运行之前,该变量将一直保留在扩展级别。因此,您正在创建一个变量,并在任何页面呈现中共享数据,即使有不同的请求。需要使用的上下文只有在请求完成之前是活动的。为此,可以使用
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)

我能够重现这个问题,这是一个与flask环境初始化有关的问题,当您添加模板处理器时会发生这个问题。但是我无法让
{{pull}
部分在一个矿井中工作。你能分享完整的代码吗?这样我就可以测试这个方法了?我有一个可能的问题fix@tarun我将添加链接