Розробка кастомних StreamField блоків Wagtail

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка кастомних StreamField блоків Wagtail
Середня
~2-3 робочих дні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка користувацьких StreamField блоків Wagtail

StreamField — механізм Wagtail, що дозволяє редакторам збирати сторінки з структурованих блоків довільного порядку. Стандартна бібліотека покриває базові випадки: текст, зображення, embed. Користувацькі блоки потрібні, коли редактор має вводити структуровані дані — карточку продукту, таблицю цін, блок з іконками та текстом — без виходу за межі CMS.

Анатомія блоку

Кожен блок в StreamField — це Python-клас, що спадкує від одного з базових типів. Найпростіший користувацький блок:

# blocks.py
from wagtail.blocks import StructBlock, CharBlock, RichTextBlock, URLBlock
from wagtail.images.blocks import ImageChooserBlock


class FeatureCardBlock(StructBlock):
    icon = ImageChooserBlock(required=False)
    heading = CharBlock(max_length=80)
    body = RichTextBlock(features=['bold', 'italic', 'link'])
    cta_text = CharBlock(max_length=40, required=False)
    cta_url = URLBlock(required=False)

    class Meta:
        icon = 'pick'
        label = 'Карточка преимущества'
        template = 'blocks/feature_card.html'

Meta.template вказує HTML-шаблон для рендеру на фронтенді. Шаблон отримує змінну value з даними блоку:

{# blocks/feature_card.html #}
<div class="feature-card">
  {% if value.icon %}
    {% image value.icon width-64 as card_icon %}
    <img src="{{ card_icon.url }}" alt="" class="feature-card__icon">
  {% endif %}
  <h3 class="feature-card__heading">{{ value.heading }}</h3>
  <div class="feature-card__body">{{ value.body }}</div>
  {% if value.cta_text and value.cta_url %}
    <a href="{{ value.cta_url }}" class="btn">{{ value.cta_text }}</a>
  {% endif %}
</div>

StructBlock з вкладеними блоками

Блоки можна вкладати. Типова задача — секція з заголовком та списком карточек:

from wagtail.blocks import ListBlock


class FeatureSectionBlock(StructBlock):
    section_title = CharBlock(max_length=120)
    layout = ChoiceBlock(choices=[
        ('grid-2', '2 колонки'),
        ('grid-3', '3 колонки'),
        ('grid-4', '4 колонки'),
    ], default='grid-3')
    cards = ListBlock(FeatureCardBlock())

    class Meta:
        icon = 'table'
        label = 'Секція з карточками'
        template = 'blocks/feature_section.html'

ListBlock обгортає будь-який блок в динамічний список — редактор додає/видаляє елементи в UI без обмежень.

StreamField в моделі сторінки

# models.py
from wagtail.models import Page
from wagtail.fields import StreamField
from wagtail.admin.panels import FieldPanel
from .blocks import FeatureSectionBlock, HeroBlock, TestimonialBlock, VideoEmbedBlock


class ServicePage(Page):
    body = StreamField([
        ('hero', HeroBlock()),
        ('features', FeatureSectionBlock()),
        ('testimonials', TestimonialBlock()),
        ('video', VideoEmbedBlock()),
    ], use_json_field=True, blank=True)

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

use_json_field=True — обов'язковий параметр починаючи з Wagtail 3.0. Дані зберігаються в jsonb колонці PostgreSQL, що дозволяє робити запити всередину структури через ORM Django.

Міграція:

python manage.py makemigrations
python manage.py migrate

Користувацький StructBlock з валідацією

Коли стандартної валідації полів недостатньо — перевизначаємо clean():

from django.core.exceptions import ValidationError
from wagtail.blocks import StreamBlockValidationError, StructBlockValidationError


class PricingBlock(StructBlock):
    plan_name = CharBlock()
    monthly_price = DecimalBlock(min_value=0)
    annual_price = DecimalBlock(min_value=0)
    features = ListBlock(CharBlock())

    def clean(self, value):
        cleaned = super().clean(value)
        errors = {}

        if cleaned['annual_price'] >= cleaned['monthly_price'] * 12:
            errors['annual_price'] = ValidationError(
                'Річна ціна має бути менше суми 12 місяців'
            )

        if len(cleaned['features']) == 0:
            errors['features'] = ValidationError(
                'Укажіть хоча б одну перевагу тарифу'
            )

        if errors:
            raise StructBlockValidationError(block_errors=errors)

        return cleaned

    class Meta:
        label = 'Тарифний план'
        template = 'blocks/pricing.html'

ChooserBlock для пов'язаних об'єктів

Якщо блок має посилатися на іншу сторінку або сніпет:

from wagtail.snippets.blocks import SnippetChooserBlock
from wagtail.blocks import PageChooserBlock


class RelatedLinksBlock(StructBlock):
    title = CharBlock(max_length=60)
    # Посилання на будь-яку сторінку сайту
    page = PageChooserBlock(required=False)
    # Посилання на сніпет (наприклад, кейс)
    case_study = SnippetChooserBlock('portfolio.CaseStudy', required=False)
    # Зовнішнє посилання
    external_url = URLBlock(required=False)

    def clean(self, value):
        cleaned = super().clean(value)
        links = [cleaned['page'], cleaned['case_study'], cleaned['external_url']]
        if not any(links):
            raise StructBlockValidationError(
                block_errors={'page': ValidationError('Укажіть хоча б одне посилання')}
            )
        return cleaned

Блок з користувацьким JavaScript в Wagtail Admin

Для складних блоків іноді потрібен власний віджет в адміністративній панелі. Wagtail 5+ підтримує Stimulus-контроллери:

from wagtail.blocks import StructBlock
from django import forms


class ColorPickerWidget(forms.TextInput):
    class Media:
        js = ['admin/js/color-picker.js']

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('attrs', {})
        kwargs['attrs']['data-controller'] = 'color-picker'
        super().__init__(*args, **kwargs)


class BrandColorBlock(StructBlock):
    label = CharBlock()
    color = CharBlock(
        form_classname='full',
        # користувацький віджет через FieldBlock
    )

Для совсім нестандартних інтерфейсів — StructBlock з перевизначеним get_form_context та користувацьким шаблоном для formset.

API та headless-режим

При використанні Wagtail як headless CMS, блоки сериалізуються через Wagtail API v2. За замовчуванням StreamField повертається як масив об'єктів {type, value, id}. Для користувацьких блоків додаємо api_representation:

class FeatureCardBlock(StructBlock):
    # ...поля...

    def get_api_representation(self, value, context=None):
        representation = super().get_api_representation(value, context)
        # додаємо вичислені поля
        if value.get('icon'):
            img = value['icon']
            representation['icon_url'] = img.file.url
            representation['icon_srcset'] = img.get_rendition('width-128').url
        return representation

Часові рамки

Один користувацький блок з шаблоном та валідацією — 2–4 години. Комплект з 8–12 блоків для типового корпоративного сайту (hero, секції, карточки, testimonials, форма, відео, галерея, таблиця цін) — 3–5 робочих днів з урахуванням вёрстки шаблонів та тестування в редакторі.