StreamField BoundBlocks와 값에 관하여

모든 StreamField 블록 유형은 페이지에 렌더링되는 방식을 결정하는 template 매개변수를 받습니다. 그러나 CharBlockIntegerBlock 과 같이 기본 Python 데이터 유형을 처리하는 블록의 경우, 해당 내장 유형(str, int 등)이 템플릿 렌더링에 대해 ‘학습’될 수 없기 때문에 템플릿이 적용되는 곳에 일부 제한이 있습니다. 이에 대한 예시로, 다음 블록 정의를 고려해보세요:

class HeadingBlock(blocks.CharBlock):
    class Meta:
        template = 'blocks/heading.html'

여기서 blocks/heading.html 은 다음과 같습니다:

<h1>{{ value }}</h1>

이는 일반 텍스트 필드처럼 동작하지만, 렌더링될 때마다 출력을 <h1> 태그로 감싸는 블록을 제공합니다:

class BlogPage(Page):
    body = StreamField([
        # ...
        ('heading', HeadingBlock()),
        # ...
    ])
{% load wagtailcore_tags %}

{% for block in page.body %}
    {% if block.block_type == 'heading' %}
        {% include_block block %}  {# 이 블록은 자체적으로 <h1>...</h1> 태그를 출력합니다. #}
    {% endif %}
{% endfor %}

이런 종류의 배열 - 일반 텍스트 문자열을 나타내는 값이지만, 템플릿에 출력될 때 자체적인 사용자 정의 HTML 표현을 가지는 것 - 은 일반적으로 Python에서 구현하기 매우 복잡한 일이지만, StreamField를 반복할 때 얻는 항목이 실제로 블록의 ‘원시’ 값이 아니기 때문에 여기서는 작동합니다. 대신, 각 항목은 BoundBlock 의 인스턴스로 반환됩니다 - 값과 그 블록 정의의 쌍을 나타내는 객체입니다. 블록 정의를 추적함으로써, BoundBlock 은 항상 어떤 템플릿을 렌더링할지 알고 있습니다. 기본 값 - 이 경우, 제목의 텍스트 내용 - 에 접근하려면 block.value 를 사용해야 합니다. 실제로, 페이지에 {% include_block block.value %} 를 출력하면, <h1> 태그 없이 일반 텍스트로 렌더링되는 것을 발견할 수 있습니다.

(더 정확히 말하면, StreamField를 반복할 때 반환되는 항목은 block_type 속성과 value 를 제공하는 StreamChild 클래스의 인스턴스입니다.)

경험이 풍부한 Django 개발자는 이를 Django의 폼 프레임워크에 있는 BoundField 클래스와 비교하는 것이 도움이 될 수 있습니다. 이는 폼 필드 값과 해당 폼 필드 정의의 쌍을 나타내며, 따라서 값을 HTML 폼 필드로 렌더링하는 방법을 알고 있습니다.

대부분의 경우, 이러한 내부 세부 사항에 대해 걱정할 필요가 없습니다; Wagtail은 예상되는 모든 곳에서 템플릿 렌더링을 사용할 것입니다. 그러나, 환상이 완전하지 않은 특정 경우가 있습니다 - 즉, ListBlock 또는 StructBlock 의 자식에 접근할 때입니다. 이러한 경우, BoundBlock 래퍼가 없으므로 항목은 자체 템플릿 렌더링을 알 수 없습니다. 예를 들어, HeadingBlock 이 StructBlock의 자식인 다음 설정을 고려해보세요:

class EventBlock(blocks.StructBlock):
    heading = HeadingBlock()
    description = blocks.TextBlock()
    # ...

    class Meta:
        template = 'blocks/event.html'

blocks/event.html 에서:

{% load wagtailcore_tags %}

<div class="event {% if value.heading == 'Party!' %}lots-of-balloons{% endif %}>
    {% include_block value.heading %}
    - {% include_block value.description %}
</div>

이 경우, value.headingBoundBlock 이 아닌 일반 문자열 값을 반환합니다; 이는 그렇지 않으면 {% if value.heading == 'Party!' %} 의 비교가 절대 성공하지 않을 것이기 때문에 필요합니다. 이는 다시 {% include_block value.heading %}<h1> 태그 없이 일반 문자열로 렌더링된다는 것을 의미합니다. HTML 렌더링을 얻으려면, value.bound_blocks.heading 을 통해 명시적으로 BoundBlock 인스턴스에 접근해야 합니다:

{% load wagtailcore_tags %}

<div class="event {% if value.heading == 'Party!' %}lots-of-balloons{% endif %}>
    {% include_block value.bound_blocks.heading %}
    - {% include_block value.description %}
</div>

실제로는, EventBlock의 템플릿에서 <h1> 태그를 명시적으로 만드는 것이 더 자연스럽고 읽기 쉬울 것입니다:

{% load wagtailcore_tags %}

<div class="event {% if value.heading == 'Party!' %}lots-of-balloons{% endif %}>
    <h1>{{ value.heading }}</h1>
    - {% include_block value.description %}
</div>

이 제한은 StructBlock의 자식으로서의 StructBlock 및 StreamBlock 값에는 적용되지 않습니다. Wagtail이 이를 BoundBlock 으로 래핑되지 않은 경우에도 자체 템플릿 렌더링을 아는 복잡한 객체로 구현하기 때문입니다. 예를 들어, StructBlock이 다음과 같이 다른 StructBlock에 중첩된 경우:

class EventBlock(blocks.StructBlock):
    heading = HeadingBlock()
    description = blocks.TextBlock()
    guest_speaker = blocks.StructBlock([
        ('first_name', blocks.CharBlock()),
        ('surname', blocks.CharBlock()),
        ('photo', ImageChooserBlock()),
    ], template='blocks/speaker.html')

EventBlock의 템플릿 내에서 {% include_block value.guest_speaker %} 는 의도한 대로 blocks/speaker.html 의 템플릿 렌더링을 선택할 것입니다.

요약하면, BoundBlocks와 일반 값 사이의 상호 작용은 다음 규칙에 따라 작동합니다:

  1. StreamField 또는 StreamBlock의 값을 반복할 때(예: {% for block in page.body %}), 일련의 BoundBlocks를 반환받게 됩니다.

  2. BoundBlock 인스턴스가 있다면, block.value 로 일반 값에 접근할 수 있습니다.

  3. StructBlock의 자식에 접근할 때(예: value.heading), 일반 값을 반환합니다; 대신 BoundBlock을 검색하려면 value.bound_blocks.heading 을 사용하세요.

  4. 마찬가지로, ListBlock의 자식에 접근할 때(예: for item in value), 일반 값을 반환합니다; 대신 BoundBlocks를 검색하려면 value.bound_blocks 를 사용하세요.

  5. StructBlock과 StreamBlock 값은 일반 값만 있고 BoundBlock이 없더라도 항상 자체 템플릿을 렌더링하는 방법을 알고 있습니다.