_Фактический шаблон, в котором я впервые столкнулся с проблемой, был более шаблонным, но этот описывает проблему более интуитивно понятным образом._
Предположим, у нас есть следующий шаблон:
{%- macro _tag(tag, id=none, classes=(), attrs={}) -%}
<{{ tag }}
{% if id %} id="{{ id }}" {% endif %}
{% if classes %} class="{{ classes|join(' ') }}" {% endif %}
{{ attrs|xmlattr }}>
{% if caller %} {{ caller() }} {% endif %}
</{{ tag }}>
{%- endmacro -%}
вместе с шаблоном более высокого уровня, который пытается {% call %}
для некоторого конкретного случая:
{%- macro _div() -%}
{% call _tag('div', *varargs, **kwargs) %}
{% autoescape false %}
{% if caller %} {{ caller() }} {% endif %}
{% endautoescape %}
{% endcall %}
{%- endmacro -%}
Интуитивно, выполнение {% call _div(...) %} <b>foo</b> {% endcall %}
должно передать <b>foo</b>
_tag
, который затем отобразит его (дословно, благодаря автоэкранированию). Но фактический результат UndefinedError: No caller defined
в строке {{ caller() }}
внутри _div
. Казалось бы, область действия caller
не распространяется на блок {% call %}
, тем самым предотвращая связанные вызовы, подобные изображенному выше.
Существует уродливый обходной путь, который использует новую функцию назначения блоков из 2.8:
{%- macro _div() -%}
{% set content %}
{% if caller %} {{ caller() }} {% endif %}
{% endset %}
{% call _tag('div', *varargs, **kwargs) %}
{% autoescape false %} {{ content }} {% endautoescape %}
{% endcall %}
{%- endmacro -%}
но, конечно, первоначальный вариант был бы гораздо предпочтительнее.
Я могу воспроизвести это с помощью этого небольшого примера:
{% macro a() %}
start of a
{% call b() %}
{{ caller() }}
{% endcall %}
end of a
{% endmacro %}
{% macro b() %}
start of b
{{ caller() }}
end of b
{% endmacro %}
{% call b() %}
inside b only
{% endcall %}
{#
{% call a() %}
inside a
{% endcall %}
#}
Это отлично работает как есть, когда последний блок раскомментирован, мы получаем следующую трассировку:
Traceback (most recent call last):
...
File "/usr/lib/python2.7/dist-packages/jinja2/environment.py", line 894, in render
return self.environment.handle_exception(exc_info, True)
File "templates/test.j2", line 19, in top-level template code
{% call a() %}
File "templates/test.j2", line 3, in template
{% call b() %}
File "templates/test.j2", line 11, in template
{{ caller() }}
File "templates/test.j2", line 4, in template
{{ caller() }}
jinja2.exceptions.UndefinedError: No caller defined
Небольшое примечание о обходном пути:
Новая функция 2.8 не нужна, так как она тоже работает нормально:
{% macro a() %}
start of a
{% set content=caller() %}
{% call b() %}
{{ content }}
{% endcall %}
end of a
{% endmacro %}
{% macro b() %}
start of b
{{ caller() }}
end of b
{% endmacro %}
{% call b() %}
inside b only
{% endcall %}
{% call a() %}
inside a
{% endcall %}
Это сделано намеренно, так как всегда ограничивается ближайшим макросом. Закрытие как wontfix.
К вашему сведению, {% set caller_ = caller %}
также работает нормально, тогда его можно вызывать всякий раз, когда это необходимо.
В любом случае, это может стоить записи в FAQ. Я помню, как некоторое время назад у коллеги была точно такая же проблема, пока мы не выяснили, что {% set caller_ = caller %}
перед переходом в другой блок вызовов помогает.
Да, возможно, мы захотим внести это в документы.
Самый полезный комментарий
Небольшое примечание о обходном пути:
Новая функция 2.8 не нужна, так как она тоже работает нормально: