Jinja嵌套渲染变量内容

12

假设我有一个名为X的变量,其中包含实际的jinja模板代码。例如,X的内容是 "{{ some_other_variable }}"。

如何在显示X的同时渲染其内容?

例如,下面的方法不起作用:

{{ X }}

它只会将"{{ some_other_variable }}"直接呈现到屏幕上,而不会呈现some_other_variable的内容。

我这样做的原因是我有一个网站,在该网站上(可信任的)用户可以创建帖子,这些帖子本身可能包含jinja模板代码。查看页面会显示这些帖子,但由于上述问题,它们会直接呈现,而不是像我想要的那样替换变量。

7个回答

15

我知道有点晚了 :) 但这里有一种解决方案,不会影响模板代码:

import jinja2
def recursive_render(tpl, values):
     prev = tpl
     while True:
         curr = jinja2.Template(prev).render(**values)
         if curr != prev:
             prev = curr
         else:
             return curr

测试运行:

>>> recursive_render("Hello {{X}}!", dict(X="{{name}}", name="world"))
u'Hello world!'

请注意,这并不是非常高效的方式,因为模板必须在每次迭代时都从头开始重新解析。


3

创建新的过滤器:

from jinja2 import contextfilter, Markup

@contextfilter
def subrender_filter(context, value):
    _template = context.eval_ctx.environment.from_string(value)
    result = _template.render(**context)
    if context.eval_ctx.autoescape:
        result = Markup(result)
    return result

env = Environment()
env.filters['subrender'] = subrender_filter

然后在模板中使用它:

{{ "Hello, {{name}}"|subrender }}

2

我使用环境的finalize选项得出了这个结果:

def render (tpl, args):
    scope = {}
    scope['env'] = jinja2.Environment (finalize=lambda x: scope['env'].from_string (x).render (**args) if isinstance(x, str) and '{{' in x else x)
    scope['env'].filters['q'] = lambda value: re.sub (r'"', r'\\"', value)
    return scope['env'].from_string (tpl).render (**args)

这不太美观,欢迎改进 :)

但它可以进行递归渲染,就像OP所要求的那样,并且直到输出中没有更多的{{为止才停止。

示例会话

>>> import jinja2
>>> def render (tpl, args):
...         scope = {}
...         scope['env'] = jinja2.Environment (finalize=lambda x: scope['env'].from_string (x).render (**args) if isinstance(x, str) and '{{' in x else x)
...         scope['env'].filters['q'] = lambda value: re.sub (r'"', r'\\"', value)
...         return scope['env'].from_string (tpl).render (**args)
... 
>>> render("this {{ outer_var }} wokring!", {"outer_var":"{{ inner_var }}", "inner_var":"{{ final_step }}", "final_step":"is"})
u'this is wokring!'

编辑:

好的,我稍微重构了我的渲染函数,功能上相同,但更加整洁:

def render (tpl, args):
    @jinja2.environmentfunction
    def finalize (env, value):
        if isinstance(value, (str, unicode)) and '{{' in value:
            return env.from_string (value).render (args)
        return value
    env = jinja2.Environment (finalize=finalize)
    env.filters['q'] = lambda value: re.sub (r'"', r'\\"', value)
    return env.from_string (tpl).render (args)

2
我想到了一个有趣的方法来解决这个问题:
  • 建立一个字典
  • 在它周围包装一个DictLoader(但保持对字典的引用)
  • 传递一个ChainLoader,其中包括DictLoader和你的常规Loader到环境中
  • 实现一个自定义过滤器,将其参数添加到上述字典中
  • 使用include指令在当前上下文中调用模板代码
尚未尝试,但可能会起作用!

自定义过滤器可以做任何事情,甚至不需要步骤1到3。你可以通过jinja传递变量。还有其他什么能够处理变量替换的,只使用jinja而不是像这样手动操作的吗? - Gagandeep Singh
啥?这个过滤器只是将模板内容传递并将其放入模板字典中,以便Jinja可以按照通常的方式处理它们。棘手的部分是获取Jinja的上下文(以便您可以使用它来处理模板)。另外,为什么不想使用自定义过滤器呢? - djc

1
我建议将X定义为宏。
import jinja2
tpl = '''
{% macro X(name) -%}
    {{name}}
{%- endmacro %}
Hello {{ X('world') }}!
'''
jinja2.Template(tpl).render()

0

我找不到一个好的方法来实现这种嵌套渲染,但是我可以尝试提供一些替代方案:

由于用户创建了帖子,我想 "{{some_other_variable}}" 实际上是整个帖子的一个子字符串,也是一个字符串。

我的做法会是:

X.replace("{{some_other_variable}}", some_other_variable))

然后像往常一样返回{{X}}。这样符合你想要做的吗?


Jinja2 的替换功能比这个更好。 {{ X|replace("{{Hello}}", "Goodbye") }}我需要比那更本地化的解决方案。 - Gagandeep Singh
我肯定不是最有经验的人。如果有任何特殊原因使jinja2替换更好,我会很高兴知道。此外,“native”是什么意思?是指尽可能接近Jinja2吗? - nglinh
哦,是的,我绝对是指尽可能接近Jinja2的解决方案。我只是在等待更通用的解决方案,通过它我可以在这些情况下添加整个Jinja2功能(而不仅仅是变量替换)。否则,我将提供变量替换的赏金。 - Gagandeep Singh

0

我找到了一种处理模板文件和环境全局变量的方法。

def render(template, values):
     prev = template.render(**values)
     while True:
         curr = Template(prev).render(siteinfo=config, menus=menus, blended_header=header, authors=authors, buildinfo=buildinfo, **values)
         if curr != prev:
             prev = curr
         else:
             return curr

在这个版本中,你必须在渲染函数内部传递环境全局变量,并且该函数本身必须在你的构建函数中。向其发送内容的方式是:render(template, dict(content=post, tags=tags)),其中template = env.get_template('index.html')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接