Flask自定义jinja2扩展缓存模板在新的请求之后状态 [英] Flask Custom jinja2 extension caching template states after new request

查看:223
本文介绍了Flask自定义jinja2扩展缓存模板在新的请求之后状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找创建一个类似的自定义扩展,以推动任何JavaScript代码块页面或页脚下面的指定区域。

我的版本使用Python 3.6,Flask和Jinja 2.9。不过,在更改块内的行号或内容后,我会遇到
的大问题。
内容在渲染时会出现多次。

  from jinja2 import nodes $ b $ from jinja2.ext import扩展

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):
解析令牌
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 ={} _ {}。 (标记名, (标记名,亚麻布)
自动._myScope [标记名] =调用者()
返回<! - 移动{}从行{} - &

我的模板代码如下所示:

 < HTML> < HEAD>< /头> <身体GT; < h1>测试模板< / h1> 
{%push'js'%} X {%endpush%}
{%push'html'%} Z {%endpush%}
{%push'js'%} Y { %endpush%}
{{pull}}
< / body> < / HTML>

我的呈现输出如下:

 < HTML> < HEAD>< /头> <身体GT; < h1>测试模板< / h1> 
名称=超级测试jinja
日期=现在
<! - 从第4行移动js_4 - >
<! - 从第5行移动了html_5 - >
<! - 从第6行移至js_6 - >
{'js_4':'X','html_5':'Z','js_6':'Y'}
< / body> < / HTML>

在更改模板块的行号或内容后发生问题。

更改内容和行号后

 < HTML> < HEAD>< /头> <身体GT; < h1>测试模板< / h1> 
{%push'js'%} ABC {%endpush%}

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

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

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

 < html> < HEAD>< /头> <身体GT; < h1>测试模板< / h1> 
名称=超级测试jinja
日期=现在
<! - 从第4行移动js_4 - >
<! - 从第7行移动了html_7 - >
<! - 从第9行移至js_9 - >
{'js_4':'X ABC','html_5':'Z','js_6':'Y','js_9':'123','html_7':'Z'}
< /体> < / HTML>

这个问题会导致重复内容被添加到回复中。



有没有办法在以往的页面请求中调用扩展来重新解析模板以进行新的更改?或可能无法缓存封闭的扩展块?



我已经尝试添加下面的代码自动重新加载模板,但没有帮助的问题。
$ b

app.jinja_env.auto_reload = True

更新:代码



Jinja自定义扩展测试代码



调用 render_template_string 不会缓存,并在进行更改时正确呈现。不知道为什么 render_template 方法缓存。

解决方案

因为存在两个问题。



添加扩展名时,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__ (环境)
self._myScope = {}
environment.extend(
pull = self._myScope

$ b $ _myScope 变量是在扩展级别创建的,它将保持在那里直到烧瓶运行。所以你正在创建一个变量,并通过任何页面呈现数据,即使有不同的请求。需要的是使用直到请求为止的上下文。对于这个可以使用 jinja2.nodes.ContextReference



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

 来自jinja2导入节点
来自jinja2.ext import Extension
from jinja2.nodes import ContextReference

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

def __init __(self,environment):
super(JavascriptBuilderExtension,self).__ init __(environment)
self._myScope = {}
$ b 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):
解析令牌
tag = parser.stream .__ next __()
ctx_ref = ContextReference()

如果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])

返回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):
$ tag $ ={} _ {}。format(tagname,linenum)
$ b $ if_myScopenot in context.vars:
context.vars [_ myScope] = { }
$ b $ context.vars [_ myScope] [tagname] = caller()

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

关键更改是
- 使用 ctx_ref = ContextReference()
- 将其传递给回调参数
- 创建 _myScope in上下文如果不存在
- 为



<生成的html请求是

 < html> < HEAD>< /头> <身体GT; < h1>测试模板< / h1> 
<! - 从第2行移至js_2 - >

<! - 从第4行移动了html_4 - >

<! - 从第6行移至js_6 - >

{'js_6':标记(u'DEF'),'html_4':标记(u'z'),'js_2':标记(u'ABC')}
< ; /体> < / HTML>


I'm Looking to create a similar custom extension to push any javascript code blocks to a designated area on the page or below footer.

My version works using Python 3.6,Flask and Jinja 2.9. However I have a major issue that occurs after changing the line number or content within the blocks. The content will appear multiple times on render.

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)

My template code looks like this

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

My rendered output is below:

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

The Problem happens after I change the template block line number or content.

After changing content and line numbers

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

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

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

Render changed blocks now has prior content

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

This issue causes duplicate content to be added into the response.

Is there a way to call the extension on ever page request to re-parse the template for new changes? or Possible to not cache the enclosed extension blocks?

I have already tried adding the code below to auto reload templates but does not help the issue.

app.jinja_env.auto_reload = True

Update: Added link to test code

Jinja custom extension test code

It appears that calling render_template_string doesn't cache and renders properly when changes are made. Not sure why render_template method caches.

解决方案

Okie, so this took a lot of hours to figure out, because there were two issues.

Flask doesn't reload templates when extension are added

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

When you add the extension like above, then jinja_env gets created even before the first request. This internally check if TEMPLATES_AUTO_RELOAD is set or not, if it is not set then value of debug is checked. But as of now even our app.run(debug=True) has not been called. So that is why templating reloading doesn't get enabled

Solution is to add config manually before accessing the jinja_env

app.config['TEMPLATES_AUTO_RELOAD'] = True
app.config['EXPLAIN_TEMPLATE_LOADING'] = True
env = app.jinja_env
env.add_extension("flaskext.JavascriptBuilderExtension")

Using request context instead of global context

Next the Extension is only initialized once. So you have used below code

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

The _myScope variable is created at extension level which will remain there till flask is running. So you are creating a variable and sharing data across any page render even with different request. The need is to use context that is only live till the request is. For this one can use jinja2.nodes.ContextReference

Also since the data is now available only in context we need to use {% pull %} instead of {{ pull }}. I could not inject a variable into context from the extension. There might be a way but my experimentation around that failed. So below is the final class I used

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)

Key changes were - Get the context using ctx_ref = ContextReference() - Pass it in the callback args - Create _myScope in context if one doesn't exist - Make a separate render for pull

Now when I make the request the html generated is

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

这篇关于Flask自定义jinja2扩展缓存模板在新的请求之后状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆