You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 18:22:02