Jinja2:在模板内渲染模板
Jinja2: render template inside template
是否可以在字符串给出的另一个模板中渲染 Jinja2 模板?例如,我想要字符串
{{ s1 }}
将呈现给
Hello world
给定以下字典作为 Template.render
的参数:
{ 's1': 'Hello {{ s2 }}', 's2': 'world' }
我知道可以使用 include
标记将 s1
的内容分隔到另一个文件来完成类似的过程,但在这里我不想那样做。
我没有可以轻松测试这些想法的环境,但我正在探索气流对 jinja 模板的使用中的类似内容。
据我所知,最好的方法是显式在外部模板中渲染内部模板字符串。为此,您可能需要在参数字典中传递或导入 the Template constructor。
这是一些(未经测试的)代码:
from jinja2 import Template
template_string = '{{ Template(s1).render(s2=s2) }}'
outer_template = Template(template_string)
outer_template.render(
s1='Hello {{ s2 }}',
s2='world',
Template=Template
)
这并不像您希望的那样干净,因此我们可以通过创建一个 custom filter 来更进一步,这样我们就可以像这样使用它:
{{ s1|inner_render({"s2":s2}) }}
我认为这是一个自定义过滤器:
from jinja2 import Template
def inner_render(value, context):
return Template(value).render(context)
现在让我们假设我们想要与外部模板相同的上下文,并且 - 到底是什么 - 让我们渲染任意数量的深度级别,N
。希望一些示例用法如下所示:
{{ s1|recursive_render }}
{{ s3|recursive_render(2) }}
从我们的自定义过滤器获取上下文的一种简单方法是使用 contextfilter decorator
from jinja2 import Template
from jinja2 import contextfilter
@contextfilter
def recursive_render(context, value, N=1):
if N == 1:
val_to_render = value
else:
val_to_render = recursive_render(context, value, N-1)
return Template(value).render(context)
现在您可以执行类似 s3 = '{{ s1 }}!!!'
的操作,并且 {{ s3|recursive_render(2) }}
应该呈现为 Hello world!!!
。我想你可以更深入地通过计算括号来检测要渲染的级别。
经历了所有这些之后,我想明确指出这非常令人困惑。
虽然我确实相信我发现在我非常具体的气流使用中需要 2 个级别的渲染,但我无法想象需要比这更多的级别。
如果您正在阅读这篇文章"this is just what I need":无论您想做什么,都可以做得更有说服力。退后一步,考虑一下您可能有一个 xy problem,然后重新阅读 jinja 的文档以确保没有更好的方法。
好吧,您可以随时创建一个过滤器,例如:
@app.template_filter('t')
def trenderiza(value, obj):
rtemplate = Environment(loader=BaseLoader()).from_string(value)
return rtemplate.render(**obj)
所以如果
s1="Hello {{s2}}"
您可以从模板中筛选为:
<p>{{s1|t(dict(s2='world')}}</p>
你可以使用从 Ansible 核心偷来的低级 Jinja API。
#!/usr/bin/env python3
# Stolen from Ansible, thus licensed under GPLv3+.
from collections.abc import Mapping
from jinja2 import Template
# https://github.com/ansible/ansible/blob/13c28664ae0817068386b893858f4f6daa702052/lib/ansible/template/vars.py#L33
class CustomVars(Mapping):
'''
Helper class to template all variable content before jinja2 sees it. This is
done by hijacking the variable storage that jinja2 uses, and overriding __contains__
and __getitem__ to look like a dict.
'''
def __init__(self, templar, data):
self._data = data
self._templar = templar
def __contains__(self, k):
return k in self._data
def __iter__(self):
keys = set()
keys.update(self._data)
return iter(keys)
def __len__(self):
keys = set()
keys.update(self._data)
return len(keys)
def __getitem__(self, varname):
variable = self._data[varname]
return self._templar.template(variable)
# https://github.com/ansible/ansible/blob/13c28664ae0817068386b893858f4f6daa702052/lib/ansible/template/__init__.py#L661
class Templar:
def __init__(self, data):
self._data = data
def template(self, variable):
'''
Assume string for now.
TODO: add isinstance checks for sequence, mapping.
'''
t = Template(variable)
ctx = t.new_context(CustomVars(self, self._data), shared=True) # shared=True is important, not quite sure yet, why.
rf = t.root_render_func(ctx)
return "".join(rf)
t_str = "{{ s1 }}"
data = { 's1': 'Hello {{ s2 }}', 's2': 'world' }
t = Templar(data)
print("template result: %s" % t.template(t_str))
template result: Hello world
是否可以在字符串给出的另一个模板中渲染 Jinja2 模板?例如,我想要字符串
{{ s1 }}
将呈现给
Hello world
给定以下字典作为 Template.render
的参数:
{ 's1': 'Hello {{ s2 }}', 's2': 'world' }
我知道可以使用 include
标记将 s1
的内容分隔到另一个文件来完成类似的过程,但在这里我不想那样做。
我没有可以轻松测试这些想法的环境,但我正在探索气流对 jinja 模板的使用中的类似内容。
据我所知,最好的方法是显式在外部模板中渲染内部模板字符串。为此,您可能需要在参数字典中传递或导入 the Template constructor。
这是一些(未经测试的)代码:
from jinja2 import Template
template_string = '{{ Template(s1).render(s2=s2) }}'
outer_template = Template(template_string)
outer_template.render(
s1='Hello {{ s2 }}',
s2='world',
Template=Template
)
这并不像您希望的那样干净,因此我们可以通过创建一个 custom filter 来更进一步,这样我们就可以像这样使用它:
{{ s1|inner_render({"s2":s2}) }}
我认为这是一个自定义过滤器:
from jinja2 import Template
def inner_render(value, context):
return Template(value).render(context)
现在让我们假设我们想要与外部模板相同的上下文,并且 - 到底是什么 - 让我们渲染任意数量的深度级别,N
。希望一些示例用法如下所示:
{{ s1|recursive_render }}
{{ s3|recursive_render(2) }}
从我们的自定义过滤器获取上下文的一种简单方法是使用 contextfilter decorator
from jinja2 import Template
from jinja2 import contextfilter
@contextfilter
def recursive_render(context, value, N=1):
if N == 1:
val_to_render = value
else:
val_to_render = recursive_render(context, value, N-1)
return Template(value).render(context)
现在您可以执行类似 s3 = '{{ s1 }}!!!'
的操作,并且 {{ s3|recursive_render(2) }}
应该呈现为 Hello world!!!
。我想你可以更深入地通过计算括号来检测要渲染的级别。
经历了所有这些之后,我想明确指出这非常令人困惑。
虽然我确实相信我发现在我非常具体的气流使用中需要 2 个级别的渲染,但我无法想象需要比这更多的级别。
如果您正在阅读这篇文章"this is just what I need":无论您想做什么,都可以做得更有说服力。退后一步,考虑一下您可能有一个 xy problem,然后重新阅读 jinja 的文档以确保没有更好的方法。
好吧,您可以随时创建一个过滤器,例如:
@app.template_filter('t')
def trenderiza(value, obj):
rtemplate = Environment(loader=BaseLoader()).from_string(value)
return rtemplate.render(**obj)
所以如果
s1="Hello {{s2}}"
您可以从模板中筛选为:
<p>{{s1|t(dict(s2='world')}}</p>
你可以使用从 Ansible 核心偷来的低级 Jinja API。
#!/usr/bin/env python3
# Stolen from Ansible, thus licensed under GPLv3+.
from collections.abc import Mapping
from jinja2 import Template
# https://github.com/ansible/ansible/blob/13c28664ae0817068386b893858f4f6daa702052/lib/ansible/template/vars.py#L33
class CustomVars(Mapping):
'''
Helper class to template all variable content before jinja2 sees it. This is
done by hijacking the variable storage that jinja2 uses, and overriding __contains__
and __getitem__ to look like a dict.
'''
def __init__(self, templar, data):
self._data = data
self._templar = templar
def __contains__(self, k):
return k in self._data
def __iter__(self):
keys = set()
keys.update(self._data)
return iter(keys)
def __len__(self):
keys = set()
keys.update(self._data)
return len(keys)
def __getitem__(self, varname):
variable = self._data[varname]
return self._templar.template(variable)
# https://github.com/ansible/ansible/blob/13c28664ae0817068386b893858f4f6daa702052/lib/ansible/template/__init__.py#L661
class Templar:
def __init__(self, data):
self._data = data
def template(self, variable):
'''
Assume string for now.
TODO: add isinstance checks for sequence, mapping.
'''
t = Template(variable)
ctx = t.new_context(CustomVars(self, self._data), shared=True) # shared=True is important, not quite sure yet, why.
rf = t.root_render_func(ctx)
return "".join(rf)
t_str = "{{ s1 }}"
data = { 's1': 'Hello {{ s2 }}', 's2': 'world' }
t = Templar(data)
print("template result: %s" % t.template(t_str))
template result: Hello world