StreamField BoundBlocks와 값에 관하여¶
모든 StreamField 블록 유형은 페이지에 렌더링되는 방식을 결정하는 template 매개변수를 받습니다. 그러나 CharBlock 과 IntegerBlock 과 같이 기본 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.heading 은 BoundBlock 이 아닌 일반 문자열 값을 반환합니다; 이는 그렇지 않으면 {% 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와 일반 값 사이의 상호 작용은 다음 규칙에 따라 작동합니다:
StreamField 또는 StreamBlock의 값을 반복할 때(예:
{% for block in page.body %}), 일련의 BoundBlocks를 반환받게 됩니다.BoundBlock 인스턴스가 있다면,
block.value로 일반 값에 접근할 수 있습니다.StructBlock의 자식에 접근할 때(예:
value.heading), 일반 값을 반환합니다; 대신 BoundBlock을 검색하려면value.bound_blocks.heading을 사용하세요.마찬가지로, ListBlock의 자식에 접근할 때(예:
for item in value), 일반 값을 반환합니다; 대신 BoundBlocks를 검색하려면value.bound_blocks를 사용하세요.StructBlock과 StreamBlock 값은 일반 값만 있고 BoundBlock이 없더라도 항상 자체 템플릿을 렌더링하는 방법을 알고 있습니다.