有没有可以进行“部分渲染”的python模板库?

时间:2012-12-27 19:14:29

标签: python template-engine

假设我有以下模板:

<!DOCTYPE html>    
<html>
    {{ var1 }}
    {% if var1 and var2 %}
        <span>some text</span>
    {% endif %}
    {{ var2 }}
</html>

当我使用var1=3作为上下文进行渲染时,它会生成此输出:

<!DOCTYPE html>    
<html>
    3
    {% if 3 and var2 %}
        <span>some text</span>
    {% endif %}
    {{ var2 }}
</html>

当我使用var2=5作为上下文再次渲染第一个渲染的输出时,输出为:

<!DOCTYPE html>    
<html>
    3
        <span>some text</span>
    5
</html>

问题是大多数模板引擎会将上下文中缺少的变量评估为空字符串。这些都是在假设只有一个渲染的情况下构建的。

我知道jinja2可以用这种方法做到这一点:Multiple renders of jinja2 templates?

但它在if语句中不起作用,它只适用于单个变量。

任何流行的模板库是否都具有我描述的渲染模式?马可?元史?别的什么?也许这是一个非python模板引擎吗?

2 个答案:

答案 0 :(得分:5)

TL; DR

我所知道的任何模板语言都不存在此功能。您最好的选择是:

  1. 使用this SO question
  2. 来解决问题
  3. 编写您自己的模板标签(见下文)
  4. 编写自己的模板标签

    我认为你在这里有几个选项,其中一些来自here,但其中没有一个涉及模板系统的内置功能。基本上,这里的目标是让渲染输出包含再次呈现的模板标记。

    正如我所看到的,最简单的方法是使用templatetag模板标签或编写自己做类似的事情,并负责转义。

    如果您可靠地首先呈现var1而第二次var2呈现:

    <!DOCTYPE html>    
    <html>
        {{ var1 }}
        {% templatetag openblock %} if {{ var1 }} and var2 {% templatetag closeblock %}
            <span>some text</span>
        {% templatetag openblock %} endif {% templatetag closeblock %}
        {% templatetag openvariable %} var2 {% templatetag closevariable %}
    </html>
    

    第一次渲染时,你会得到:

    <!DOCTYPE html>    
    <html>
        {{ 3 }}
        {% if 3 and var2 %}
            <span>some text</span>
        {% endif %}
        {{ var2 }}
    </html>
    

    在第二次渲染时,会产生所需的输出。

    显然,不断编写多层{% templatetag %}会是一个巨大的痛苦,所以我建议你编写自己的递归模板标签,为你解决这个问题,可能还有一个参数指明如何你需要多层嵌套,然后显然是输入本身的一个参数。关于这一点的好处是,如果你只需要一层嵌套,只需输出模板标签,输入就可以了,

    基本上,通过递归地使用此自定义模板标记输出,您可以非常轻松地实现尽可能多的嵌套层。假设标记实现为{% t <layers of nesting> <input> %}

        Initial template:  {% t 2 "{{ var2 }}" %}
        First rendering:   {% t 1 "{{ var2 }}" %}
        Second rendering:  {{ var2 }}
        Final rendering:   5
    

    现在,对于某些更复杂的标记,例如{% if %},这肯定会更加困难,特别是如果在您的示例中,您在单个if语句中需要多层渲染。你可能最好在这里拆分你的if语句,这样你就可以有更清晰的渲染分离。以下示例假定{% t %}标记的实现,它是{% t %} / {% endt %}组合:

    初始HTML:

    {% if var1 %}
       {% t 1 %}
           {% if var2 %}
              <span>some text</span>
           {% endif %}
       {% endt %}
    {% endif %}
    

    首先渲染:

    {# Note that the first if statement has been evaluated and is gone #}
    {% if var2 %}
        <span>some text</span>
    {% endif %}
    

    最终呈现:

    <span>some text</span>
    

答案 1 :(得分:0)

在这里重提一个老问题——我最近想做这个,发现原来的答案过于复杂。这是相同答案的更简单版本。 TLDR;使用扩展程序。

为此做一个扩展是相当简单的。这是我用来部分呈现模板的一个扩展版本。我使用自定义标签 preserveendpreserve 来分隔我不想在第一遍呈现的块。

此模板的渲染结果仅用于创建另一个模板,该模板可以在第二次传递期间具有未渲染的变量等。

用法。

text = ...
pass_1 = Template(text, extensions=[PreserveExtension])
result_1 = pass_1.render(...)
pass_2 = Template(pass_1)
final_render = pass_2.render(...)

这是扩展名。

class PreserveExtension(Extension):

  """
  Extensions ignores jinja templates between tags.
  NOTE: Preserved template spacing is slightly modified.

  Example Input:

    this is plain text
    {%- preserve %}
    this is plain text
    {{ a }}
    {%- if b %}{{ b }}{% endif -%}
    {% for i in c -%}
      {{ i }}
    {%- endfor %}
    {%- endpreserve %}

  Example Output:

    this is plain text
    this is plain text
    {{ a }}{%- if b %} {{ b }} {% endif -%}
    {% for i in c -%}
      {{ i }}{%- endfor %}
  """

  tags = {"preserve"}

  def parse(self, parser: Parser):
    lineno = parser.stream.current.lineno

    parser.parse_expression()
    parser.stream.skip()

    body = []
    raw = []

    def flush():
      nonlocal raw
      nonlocal body
      node = nodes.TemplateData(''.join(raw))
      body.append(node)
      raw = []

    while True:
      t: Token = next(parser.stream)
      if t.lineno != lineno:
        flush()
        lineno = t.lineno
      test = t.test('name:endpreserve')
      if test:
        raw.pop(-1)
        break
      if raw and not raw[-1].endswith('\n'):
        raw.append(' ')
      raw.append(t.value)
    if raw:
      flush()
    return body