Jinja: Falha ao usar {{ caller() }} em {% call %}

Criado em 23 set. 2014  ·  5Comentários  ·  Fonte: pallets/jinja

_O modelo real onde encontrei o problema primeiro era mais clichê, mas este descreve o problema de uma maneira mais intuitiva._

Suponha que temos o seguinte modelo:

{%- 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 -%}

juntamente com um modelo de nível superior que tenta {% call %} para algum caso específico:

{%- macro _div() -%}
  {% call _tag('div', *varargs, **kwargs) %}
    {% autoescape false %}
      {% if caller %} {{ caller() }} {% endif %}
    {% endautoescape %}
  {% endcall %}
{%- endmacro -%}

Intuitivamente, fazer {% call _div(...) %} <b>foo</b> {% endcall %} deve passar <b>foo</b> para a macro _tag , que então a renderiza (literalmente, graças ao autoescape). Mas o resultado real é UndefinedError: No caller defined na linha {{ caller() }} dentro _div . Parece que o escopo de caller não se estende ao bloco {% call %} , evitando assim chamadas encadeadas como a mostrada acima.

Existe uma solução feia que usa o novo recurso de atribuição de bloco do 2.8:

{%- macro _div() -%}
  {% set content %}
    {% if caller %} {{ caller() }} {% endif %}
  {% endset %}
  {% call _tag('div', *varargs, **kwargs) %}
    {% autoescape false %} {{ content }} {% endautoescape %}
  {% endcall %}
{%- endmacro -%}

mas, claro, a versão original seria muito mais preferível.

Comentários muito úteis

Pequena observação sobre a solução alternativa:

O novo recurso do 2.8 não é necessário, pois também está funcionando bem:

{% 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 %}

Todos 5 comentários

Eu sou capaz de reproduzir isso com este pequeno exemplo:

{% 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 %}
#}

Isso funciona bem como está, quando o último bloco é descomentado, obtemos o seguinte traceback:

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

Pequena observação sobre a solução alternativa:

O novo recurso do 2.8 não é necessário, pois também está funcionando bem:

{% 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 %}

Isso é meio que intencional, pois sempre abrange a macro mais próxima. Fechando como não vai corrigir.

Para sua informação, {% set caller_ = caller %} também funciona bem, então pode ser chamado sempre que necessário.

De qualquer forma, isso pode valer a pena uma entrada de FAQ. Lembro-me de um colega tendo exatamente o mesmo problema algum tempo atrás, até descobrirmos que {% set caller_ = caller %} antes de entrar em outro bloco de chamadas ajuda.

Sim, podemos querer colocar isso nos documentos.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

samatjain picture samatjain  ·  5Comentários

humiaozuzu picture humiaozuzu  ·  3Comentários

mitsuhiko picture mitsuhiko  ·  3Comentários

priestc picture priestc  ·  5Comentários

harobed picture harobed  ·  6Comentários