Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jinja2 - Keep undefined variables

I am interested in rendering a template in multiple steps or keeping the tags for the undefined variables in Jinja2. I believe this would mean not only creating the 'UndefinedSilent" class (so the template won't crash on missing data) but also keeping the tags with the appropriate variable names if they are missing.

Example: Let's say we have the name = "Test" included in the context, but quantity is missing.

Givent the following template:

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

After rendering, I need the template to become:

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

Does anyone know if this is achievable?

like image 537
vrwolf Avatar asked Feb 24 '15 09:02

vrwolf


3 Answers

Providing DebugUndefined to named parameter undefined in the env, apparently does the trick. The rendered template preserves the {{<undefined variable}}.

Like here:

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"}))

The result is:

a is defined, but {{ b }} is undefined
like image 180
giwyni Avatar answered Dec 02 '22 20:12

giwyni


It is achievable using the default built-in filter.

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

The drawback is that the code becomes uglier and the variable names are duplicated, thus reducing maintainability.

like image 38
M. F. Avatar answered Dec 02 '22 21:12

M. F.


Here is another approach that preserves undefined double curly expressions after rendering, including those that contain "multilevel" (dot-notated) references as well as any others.

The answer provided by Willy Pregliasco does not support preservation of undefined list types, eg {{ some_list[4] }} which is something I required. The below solution addresses this, as well as all possible schema types.

The idea is to parse the input template and try to resolve each expression with the provided context. Any that can not be resolved, we replace with a double curly expression that simply resolves to the original expression as a string.

Pass your template and context through the below preserve_undefineds_in_template function before calling render:

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

Example:

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)

The output is:

hello Alice, {{ preserve_me }} {{ preserve.me[2] }}
like image 24
George Murdocca Avatar answered Dec 02 '22 19:12

George Murdocca