https://docs.wagtail.io/en/stable/topics/streamfield.html

StreamField에 대해서 소개해보려고 한다. 

Wagtail에는 이렇게 말하고 있다. 'A block-based content field' 

 

StreamField는 Custom Field라고 생각하면 된다. 고정된 구조를 따르지 않고 Custom이 가능하다. admin page에서 수정을 할때 block 형태로 여러가지 content들을 추가 가능하다.

 

쉽게 말하자면 django admin의 field set 기능이랑 비슷하다고 생각하면 된다. stream field를 사용하여 page를 생성할 때, 정돈된 형태로 작성할 수 있다.

 

streamfield를 실습하기 위해 app을 하나 만들어보자 이름은 blog로 하겠다.

python manage.py startapp blog

Setting에 Installed_app에 blog를 추가해준다.

project_folder/settings/base.py

INSTALLED_APPS = [
    'home',
    'search',
    'card',
    'member',
    'classroom',
    # 테스트입니다.
    'blog',

이제 모델을 작성해보자.

from django.db import models

from wagtail.core.models import Page
from wagtail.core.fields import StreamField
from wagtail.core import blocks
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.images.blocks import ImageChooserBlock

class BlogPage(Page):
    author = models.CharField(max_length=255)
    date = models.DateField("Post date")
    body = StreamField([
        ('heading', blocks.CharBlock(form_classname="full title")),
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
    ])

    content_panels = Page.content_panels + [
        FieldPanel('author'),
        FieldPanel('date'),
        StreamFieldPanel('body'),
    ]

모델을 작성했으니 makemigrations와 migrate를 진행하자.

 

templates 폴더를 만들고 blog_page.html를 작성해보자

templates/blog/blog_page.html

{% extends "base.html" %}

{% load wagtailcore_tags %}

<article>
    {% for block in page.body %}
        {% if block.block_type == 'heading' %}
            <h1>{{ block.value }}</h1>
        {% else %}
            <section class="block-{{ block.block_type }}">
                {% include_block block %}
            </section>
        {% endif %}
    {% endfor %}
</article>

여기서 streamfield는 body라는 변수로 정의를 해놓았기 때문에 page..body로 streamfield의 내용을 출력할 수 있다.

for 반복문 안에서 if문을 사용하여 표시하고자 하는 streamfield안에 있는 field 값을 호출 해올 수 있다. 

이는 .value를 사용하여 가능하다. 

그냥 field값을 계속 출력하고 싶으면 

{% include_block block %}로 작성하면 된다.

 

python manage.py runserver를 하게 되면 admin 페이지에서 streamfield가 생성 됨을 볼 수 있다. 

간단하게 기본 사용법을 알아보았는데 사실 streamfield custom하는 방법은 엄청 많다. 

StructBlock

struckblock은 streamfield 안에 한번 더 streamfield를 정의 해준 느낌이다. 아래 코드에서는 이름에 관련 된 '자식' 블록을 생성하여 작성하였다. 

 body = StreamField([
     ('person', blocks.StructBlock([
         ('first_name', blocks.CharBlock()),
         ('surname', blocks.CharBlock()),
         ('photo', ImageChooserBlock(required=False)),
         ('biography', blocks.RichTextBlock()),
     ])),
     ('heading', blocks.CharBlock(form_classname="full title")),
     ('paragraph', blocks.RichTextBlock()),
     ('image', ImageChooserBlock()),
 ])

template에 작성할 때는 아래와 같이 작성한다. 

 

<article>
    {% for block in page.body %}
        {% if block.block_type == 'person' %}
            <div class="person">
                {% image block.value.photo width-400 %}
                <h2>{{ block.value.first_name }} {{ block.value.surname }}</h2>
                {{ block.value.biography }}
            </div>
        {% else %}
            (rendering for other block types)
        {% endif %}
    {% endfor %}
</article>

 

Subclassing StructBlock

Structblock안에 child block을 정의 해주면 읽기도 불편하고 재사용하기도 힘들다. 그래서 차라리 sub class를 만들어서 사용하는 방법에 대해서 알아보려고 한다. 

class PersonBlock(blocks.StructBlock):
    first_name = blocks.CharBlock()
    surname = blocks.CharBlock()
    photo = ImageChooserBlock(required=False)
    biography = blocks.RichTextBlock()

이렇게 sub class를 models.py에 정의해주자.

body = StreamField([
    ('person', PersonBlock()),
    ('heading', blocks.CharBlock(form_classname="full title")),
    ('paragraph', blocks.RichTextBlock()),
    ('image', ImageChooserBlock()),
])

그리고 클래스를 호출해오게 되면 전과 같은 기능을 하는 것을 확인할 수 있다.

 

Block Icons

 

streamfield에 icon을 추가 해줄 수 있다. page instance를 생성할 때, icon을 통해서 각각의 block 을 구별 해줄 수 있다. 

UI Guide

자세한건 UI Guide 문서를 참고하면 될 것이다.

일단 settings에 base.py에 installed_app에 styleguide를 입력하자

INSTALLED_APPS = (
    ...
    'wagtail.contrib.styleguide',
    ...
)

그리고 models.py에 icon을 추가해주자.

 body = StreamField([
     ('person', blocks.StructBlock([
         ('first_name', blocks.CharBlock()),
         ('surname', blocks.CharBlock()),
         ('photo', ImageChooserBlock(required=False)),
         ('biography', blocks.RichTextBlock()),
     ], icon='user')),
     ('heading', blocks.CharBlock(form_classname="full title")),
     ('paragraph', blocks.RichTextBlock()),
     ('image', ImageChooserBlock()),
 ])
 class PersonBlock(blocks.StructBlock):
     first_name = blocks.CharBlock()
     surname = blocks.CharBlock()
     photo = ImageChooserBlock(required=False)
     biography = blocks.RichTextBlock()

     class Meta:
         icon = 'user'

amdin을 들어가서 생성을 해보면 아이콘이 잘 적용되어 있는 것을 확인 할 수 있을 것이다.

 

ListBlock

 

listblock을 생성해보자 listblock은 반복되는 block을 뜻하는데 생성해야할 instance가 많을 때, 사용한다. 형태를 보면 바로 이해가 될 것이다. 

 body = StreamField([
     ('gallery', blocks.ListBlock(ImageChooserBlock())),
     ('heading', blocks.CharBlock(form_classname="full title")),
     ('paragraph', blocks.RichTextBlock()),
     ('image', ImageChooserBlock()),
 ])
<article>
    {% for block in page.body %}
        {% if block.block_type == 'gallery' %}
            <ul class="gallery">
                {% for img in block.value %}
                    <li>{% image img width-400 %}</li>
                {% endfor %}
            </ul>
        {% else %}
            (rendering for other block types)
        {% endif %}
    {% endfor %}
</article>

슬라이드나 여러 이미지를 표시해야할 때, 효과적으로 관리해줄 수 있을 것이다.

<영상을 추가하고 싶을 때는 from wagtail.embeds.blocks import EmbedBlock 을 사용한다.>

 

Limiting block counts

default에서는 streamfield에서 block을 숫자 제한 없이 사용할 수 있다. min_num과 max_num을 사용하면 Streamblock의 수를 제한할 수 있다.

4가지 방법이 있는데 코드만 집고 넘어가보자

body = StreamField([
    ('heading', blocks.CharBlock(form_classname="full title")),
    ('paragraph', blocks.RichTextBlock()),
    ('image', ImageChooserBlock()),
], min_num=2, max_num=5)
class CommonContentBlock(blocks.StreamBlock):
    heading = blocks.CharBlock(form_classname="full title")
    paragraph = blocks.RichTextBlock()
    image = ImageChooserBlock()

    class Meta:
        min_num = 2
        max_num = 5
body = StreamField([
    ('heading', blocks.CharBlock(form_classname="full title")),
    ('paragraph', blocks.RichTextBlock()),
    ('image', ImageChooserBlock()),
], block_counts={
    'heading': {'min_num': 1, 'max_num': 3},
})
class CommonContentBlock(blocks.StreamBlock):
    heading = blocks.CharBlock(form_classname="full title")
    paragraph = blocks.RichTextBlock()
    image = ImageChooserBlock()

    class Meta:
        block_counts = {
            'heading': {'min_num': 1, 'max_num': 3},
        }

 

 

추가) StreamField 사용

blocks.py

from wagtail.core import blocks
from wagtail.images.blocks import ImageChooserBlock


class TitleAndTextBlock(blocks.StructBlock):
    """Title and text and nothing else"""

    title = blocks.CharBlock(required=True, help_text="Add your title")
    text = blocks.TextBlock(required=True, help_text='Add additional text')
    
    class Meta:
        template = "streams/title_and_text_block.html"
        icon = "edit"
        label = "Title & Text"
        
        
class CardBlock(blocks.StructBlock):
    """Cards with image and text and button(s)."""
    
    title = blocks.CharBlock(required=True, help_text="Add your title")
    
    cards = blocks.ListBlock(
        blocks.StructBlock(
            [
                ("image", ImageChooserBlock(required=True)),
                ("title", blocks.CharBlock(required=True, max_length=40)),
                ("text", blocks.TextBlock(required=True, max_length=200)),
                ("button_page", blocks.PageChooserBlock(required=False)),
                ("button_url", blocks.URLBlock(required=False, help_text="If the button page above is selected, that will be used first."))
            ]
        )
    )
    
    class Meta: # noqa
        template = "streams/card_block.html"
        icon = "placeholder"
        label = "Staff Cards"   


class RichtextBlock(blocks.RichTextBlock):
    """Richtext without (limited) all the features"""
    
    class Meta: # noqa
        template = "streams/richtext_block.html"
        icon = "doc-full"
        label = "Full RichText"       
        
        
class SimpleRichtextBlock(blocks.RichTextBlock):
    """Richtext without (limited) all the features"""
    
    def __init__(self, required=True, help_text=None, editor='default', features=None, validators=(), **kwargs):
        super().__init__(**kwargs)
        self.features = [
            "bold",
            "italic",
            "link",
        ]

    class Meta: # noqa
        template = "streams/simple_richtext_block.html"
        icon = "edit"
        label = "Simple RichText"
        
        
class CTABlock(blocks.StructBlock):
    """A simple call to action section"""
    
    title = blocks.CharBlock(required=True, max_length=60)
    text = blocks.RichTextBlock(required=True, features=["bold","italic"])
    button_page = blocks.PageChooserBlock(required=False)
    button_url = blocks.URLBlock(required=False)
    button_text = blocks.CharBlock(required=True, default='Learn More', max_length=40)
    
    class Meta:
        template = "streams/cta_block.html"
        icon = "placeholder"
        label = "Call to Action"

위와 같은 StreamBlock 구조로 작성하였다.

각 Block마다 template이 정의되어 있다. 

 

 

1) RichTextBlock.html

{{ self }}

특별한 구조가 없으므로 간단하기 자기 자신을 가르키는 self로 작성한다.

 

 

2) CardBlock.html

{% load wagtailimages_tags %}

<div class="container">
    <h1 class="text-center">{{ self.title }}</h1>
    <div class="card-deck">
        {% for card in self.cards %}
            {% image card.image fill-300x200 as img %}
            <div class="card">
                <img src="{{ img.url }}" class="card-img-top" alt="{{ img.alt }}">
                <div class="card-body">
                    <h5 class="card-title">{{ card.title }}</h5>
                    <p class="card-text">{{ card.text }}</p>
                    {% if card.button_page %}
                        <a href="{{ card.button_page.url }}" class="btn btn-primary">
                            Learn More (internal)
                        </a>
                    {% elif card.button_url %}
                        <a href="{{ card.button_url }}" class="btn btn-primary">
                            Learn More (external)
                        </a>
                    {% endif %}
                </div>
            </div>
        {% endfor %}
    </div>    
</div>

title이라는 CharBlock과 cards라는 ListBlock으로 작성되어 있다. ListBlock은 StruckBlock으로 되어 있고 여러 Block들을 가지고 있다. 일반적인 Wagtail templates 작성처럼 해주면 된다. 

 

그렇다면 Streams의 Templates에서 말고 실제 보여지는 Template에서는 작성을 어떻게 할까?

 

    {% for block in page.content %}
        {% include_block block %}
    {% endfor %}

위와 같이 작성을 해주면 block에 정의된 Template으로 화면에 표현이 된다. 

'Back-End > Wagtail, Django' 카테고리의 다른 글

Wagtail demo site (breads)  (0) 2021.08.17
Wagtail demo site (blog)  (0) 2021.08.17
Wagtail demo site (base)  (0) 2021.08.15
Wagtail 이미지  (0) 2021.08.13
Wagtail tutorial (튜토리얼)  (0) 2021.08.11

+ Recent posts