Jinja2(Flask):如何向宏传递多段HTML/Jinja代码块?
关于Jinja宏接收多段代码块的问题
问题背景
我在开发Python Flask Web应用时,尝试编写可接收2-3段HTML/Jinja代码块的宏,但运行出错。参考示例代码后触发报错:jinja2.exceptions.TemplateAssertionError: block 'content' defined twice,原示例代码如下:
{% macro panel(title, class='panel') %} <div class="{{ class }}"> <h2>{{ title }}</h2> {% block content %}{% endblock %} </div> {% endmacro %} {% call(panel, title='My Panel') %} {% block content %} <p>This is a panel content.</p> {% endblock %} {% endcall %}
我想确认这个示例是否存在问题,同时希望找到更优的向宏传递多段代码块的方法——我知道caller方式,但误以为它只支持单个代码块。
实际需求
我的真实需求是简化Bootstrap Accordion的重复使用,需要向宏传递多段代码块,扩展示例如下:
{% macro panel(title, class='panel') %} <div class="{{ class }}"> <h2>{{ title }}</h2> {% block content1 %}{% endblock %} <!-- Do some other stuff in between. --> {% block content2 %}{% endblock %} </div> {% endmacro %} {% call(panel, title='My Panel') %} {% block content1 %} <p>This is a panel content.</p> {% endblock %} {% block content2 %} <p>This is the detailed panel content.</p> {% endblock %} {% endcall %}
当前实现(模板继承)
目前我通过模板继承实现了功能,但更倾向于使用call/caller方案,相关代码如下:
mymacro.j2
<!doctype html> <html> <body> {% macro panel(title, class='panel') %} <div class="{{ class }}"> <h2>{{ title }}</h2> {% block content1 %}{% endblock %} <h3> Stuff in between. </h3> {% block content2 %}{% endblock %} </div> {% endmacro %} {% block macrocall %}{% endblock %} </body> </html>
main.j2
{% extends "mymacro.j2" %} {% block content1 %} <p>Panel content1.</p> {% endblock %} {% block content2 %} <p>This is content2.</p> {% endblock %} {% block macrocall %} {{ panel(title='My Panel') }} {% endblock %}
解决方案
问题根源
原示例的错误在于:Jinja的block是为模板继承设计的,不能在call标签内部使用——宏里定义的block和call里的block会被视为全局重复定义,从而触发报错。
方法1:带参数的caller(支持多段代码块)
Jinja的caller其实支持传递参数,你可以在宏里多次调用caller并传入不同标识,然后在call内部根据标识返回对应代码块。示例:
{% macro panel(title, class='panel') %} <div class="{{ class }}"> <h2>{{ title }}</h2> {{ caller('content1') }} <!-- 中间固定内容 --> <h3>Stuff in between.</h3> {{ caller('content2') }} </div> {% endmacro %} {% call panel(title='My Panel') as content_type %} {% if content_type == 'content1' %} <p>This is a panel content.</p> {% elif content_type == 'content2' %} <p>This is the detailed panel content.</p> {% endif %} {% endcall %}
方法2:将代码块作为宏参数传递
把每个需要传入的代码块定义成独立的小宏,然后作为参数传递给主宏:
{% macro content1() %} <p>This is a panel content.</p> {% endmacro %} {% macro content2() %} <p>This is the detailed panel content.</p> {% endmacro %} {% macro panel(title, content1_block, content2_block, class='panel') %} <div class="{{ class }}"> <h2>{{ title }}</h2> {{ content1_block() }} <h3>Stuff in between.</h3> {{ content2_block() }} </div> {% endmacro %} {{ panel(title='My Panel', content1_block=content1, content2_block=content2) }}
两种方法对比
- 方法1更贴合call/caller的使用场景,代码结构紧凑,适合需要在宏内部多次插入动态内容的场景。
- 方法2逻辑更直观,每个代码块独立定义,便于复用和维护。
内容的提问来源于stack exchange,提问作者Jerome
相关产品推荐
相关产品推荐

