Flask自定义jinja2扩展缓存模板在新的请求之后状态 [英] Flask Custom jinja2 extension caching template states after new request
问题描述
我正在寻找创建一个类似的自定义扩展,以推动任何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
更新:代码
调用 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屋!