保留未定义的变量

Keep undefined variables

我有兴趣在多个步骤中呈现模板或保留 Jinja2 中未定义变量的标签。我相信这不仅意味着创建“UndefinedSilent”class(因此模板不会因丢失数据而崩溃)而且还意味着在标签丢失时使用适当的变量名称。

示例: 假设我们在上下文中包含 name = "Test",但缺少 quantity

给出以下模板:

<p>{{name}} has {{quantity}}</p>

渲染后,我需要模板变成:

<p>test has {{quantity}}</p>

有谁知道这是否可以实现?

使用 default built-in filter 可以实现。

<p>{{name|default('{{name}}')}} has {{quantity|default('{{quantity}}')}}</p>

缺点是代码变丑,变量名重复,降低可维护性

向环境中未定义的命名参数提供 DebugUndefined,显然可以解决问题。呈现的模板保留了 {{<undefined variable}}.

点赞:

from jinja2 import Environment, BaseLoader, DebugUndefined

rtemplate = Environment(loader=BaseLoader,undefined=DebugUndefined).from_string("{{ a }} is defined, but {{ b}} is undefined")
print(rtemplate.render({"a":"a"}))

结果是:

a is defined, but {{ b }} is undefined

我也看到了同样的行为。库 jinja2schema 提供模板所需的变量架构。

我的解决步骤是:

  • 有模板
  • 获取模式结构
  • 填写一些数据
  • 完整的de数据用原始字符串填充缺失的项目
from jinja2 import Template
import jinja2schema

def assign(schema, data, root=''):
    '''Returns a corrected data with untouched missing fields

    '''

    out = {}
    for key in schema.keys():

        if isinstance(schema[key], (str, jinja2schema.model.Scalar)): 
            try:
                out[key] = data[key]
            except:
                out[key] = f'{{{{ {root+key} }}}}'

        elif isinstance(schema[key], (dict, jinja2schema.model.Dictionary)):
            out[key]={}
            try:
                data[key]
            except:
                data[key] = {}
            out[key] = assign(schema[key], data[key], root+key+'.')

    return out

# original template
template_str = '<p>{{name}} has {{quantity}}</p>'
# read schema
schema = jinja2schema.infer(template_str)
# available data
data = {'name':'test'}
# data autocompleted
data_corrected = assign(schema, data)
# render
template = Template(template_str)
print(template.render(data_corrected))

输出为

<p>test has {{ quantity }}</p>

这是预期的结果。

希望对您有所帮助。该解决方案不适用于列表,但我认为可以扩展该解决方案。如果需要,您还可以获得缺失字段的列表。

这是 Template 而不是 Environment 的版本:

from jinja2 import Template, DebugUndefined

template = Template("<p>{{name}} has {{quantity}}</p>", undefined=DebugUndefined)

new_template = Template(template.render(name="Test"))

感谢@giwyni

另一个小技巧,如果你只有几个变量:

<p>{{name}} has {{ "{{quantity}}" }}</p>

第二个替换将替换为 {{quantity}} 所以一切都很好 ;)

这是另一种在渲染后保留未定义的双卷曲表达式的方法,包括那些包含“多级”(dot-notated) 引用以及任何其他引用的表达式。

Willy Pregliasco 提供的答案不支持保留未定义的列表类型,例如 {{ some_list[4] }} 这是我需要的。下面的解决方案解决了这个问题,以及所有可能的模式类型。

想法是解析输入模板并尝试使用提供的上下文解析每个表达式。任何无法解析的,我们用一个双卷曲表达式替换,该表达式简单地解析为原始表达式作为字符串。

在调用 render 之前,通过下面的 preserve_undefineds_in_template 函数传递您的模板和上下文:

from jinja2 import Template, StrictUndefined, UndefinedError
import re

def preserve_undefineds_in_template(template, context):
    patt = re.compile(r'(\{\{[^\{]*\}\})')
    j2_expressions = patt.findall(template)
    for j2_expression in set(j2_expressions):
        try:
            Template(j2_expression, undefined=StrictUndefined).render(context)
        except UndefinedError:
            template = template.replace(j2_expression, f"{{% raw %}}{j2_expression}{{% endraw %}}")
    return template

示例:

template = """hello {{ name }}, {{ preserve_me }} {{ preserve.me[2] }}"""

context = { "name": "Alice" }

# pass it through the preserver function
template = preserve_undefineds_in_template(template, context)

# template is now:
# hello {{ name }}, {% raw %}{{ preserve.me }}{% endraw %} {% raw %}{{ preserve.me.too[0] }}{% endraw %}

# render the new template as normal
result = Template(template).render(context)

print(result)

输出为:

hello Alice, {{ preserve_me }} {{ preserve.me[2] }}