Jinja2 (колба): Как передать блоки в макрос?

Я реализую веб-приложение Python Flask и пытаюсь написать макрос, в который хочу передать три блока html-кода, но не смог его запустить.

Я нашел простой пример использования jinja с fastapi здесь: https://www.slingacademy.com/article/fastapi-how-to-use-macros-in-jinja-templates/?utm_content=cmp-true Я ожидал, что это сработает, но не смог его запустить. Я попробовал и получил сообщение об ошибке: «jinja2.Exceptions.TemplateAssertionError: блок 'content' определен дважды».

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

Должен ли этот пример работать или в шаблоне jinja2 есть ошибка? Есть ли альтернативный/лучший способ передачи блоков html-кода в макрос? Я узнал подход «вызывающий», но думаю, что он работает только с одним блоком кода, верно?

Спасибо!

С уважением, Джером

РЕДАКТИРОВАТЬ Похоже, что пример сильно упрощен. Я хотел бы передать в макрос 2 или 3 блока кода HTML или даже Jinja. Дело в том, что я хочу упростить использование 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 %}

РЕДАКТИРОВАТЬ 2 Основываясь на ответе и подсказках @Detlef, я каким-то образом получил пример, работающий с наследованием шаблонов, см. ниже. В любом случае, я использую концепцию вызывающего абонента, поскольку это более очевидный способ.

моймакрос.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>

основной.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 %}

🤔 А знаете ли вы, что...
Python популярен в машинном обучении и искусственном интеллекте.


81
1

Ответ:

Решено

Команда caller() дает вам возможность динамически встраивать код в макрос, который был вызван с помощью call(). Если вы присвоите имя переменной в call() и передадите атрибут в caller(), это также можно использовать с определенным именем переменной во встроенном коде.
Вы можете найти документацию здесь.

{% macro panel(title, class_='panel') %}
<div class = "{{ class_ }}">
    <h2>{{ title }}</h2>
    {{ caller() }}
</div>
{% endmacro %}
{% call() panel(title='My Panel') %}
    <p>This is a panel content.</p>
{% endcall %}

Переданный код предназначен для соответствующего вызова.

Пока имена блоков остаются уникальными в шаблоне, вы также можете использовать несколько вложенных блоков в вызове макроса.

{% call() panel(title='My Panel') %}
    {% block content1 %}
    {% endblock %}
    {% block content2 %}
    {% endblock %}
{% endcall %}

Простой пример реализации макроса для загрузочного аккордеона.

{% macro bs_accordion(items) %}
    <div class = "accordion {{ kwargs.class_ }}" id = "{{ kwargs.id }}">
        {% for k,v in items.items() -%}
        <div class = "accordion-item">
            <h2 class = "accordion-header">
                <button 
                    class = "accordion-button {% if not loop.first %}collapsed{% endif %}" 
                    type = "button" 
                    data-bs-toggle = "collapse" 
                    data-bs-target = "#collapse-{{ loop.index }}" 
                    aria-expanded = "{{ loop.first | lower }}" 
                    aria-controls = "collapse-{{ loop.index }}"
                >{{ k }}</button>
            </h2>
            <div 
                id = "collapse-{{ loop.index }}" 
                class = "accordion-collapse collapse {% if loop.first %}show{% endif %}" 
                data-bs-parent = "#{{ kwargs.id }}"
            >
                <div class = "accordion-body">
                    {{ caller(v) }}
                </div>
            </div>
         </div>
        {% endfor -%}
    </div>
{% endmacro %}

<!DOCTYPE html>
<html>
<head>
    <meta charset = "utf-8">
    <meta name = "viewport" content = "width=device-width, initial-scale=1">
    <title>Example</title>
    <link 
        href = "https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" 
        rel = "stylesheet" 
        integrity = "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" 
        crossorigin = "anonymous">
</head>
<body>
    <main class = "container my-4">
    {% set items = { 
            'My Panel 1': ['This is the first panel.', 'This is the detailed panel content.'], 
            'My Panel 2': {'Description': 'This is the second panel.', 'Details': 'This is the detailed panel content.'}, 
            'My Panel 3': 'This is the third panel.', 
        } 
    %}

    {% call(content) bs_accordion(items, id='accordionExample') %}
        {% if content is string -%}
            {{ content }}
        {% elif content is mapping -%}
            <dl>
            {% for k,v in content.items() -%}
                <dt>{{ k }}</dt>
                <dd>{{ v }}</dd>
                {% if not loop.last -%}
                <!-- Do some other stuff in between. -->
                {% endif -%}
            {% endfor -%}
            </dl>
        {% elif content is sequence -%}
            {% for item in content -%}
                <p>{{ item }}</p>
                {% if not loop.last -%}
                <!-- Do some other stuff in between. -->
                {% endif -%}
            {% endfor -%}
        {% endif -%}
    {% endcall %}
    </main>

    <script 
        src = "https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" 
        integrity = "sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" 
        crossorigin = "anonymous"></script>
</body>
</html>