Редактори контенту в Wagtail часто впираються в обмеження стандартних блоків: неможливо зробити картку товару з рейтингом, таблицю цін з трьома колонками або блок з відео та текстом у три ряди. Ми у своїй практиці стикалися з такими запитами десятки разів. За більш ніж п'ять років роботи ми розробили понад 50 наборів кастомних блоків для проєктів від корпоративних сайтів до headless-рішень на Wagtail. Кастомні StreamField-блоки — єдиний спосіб дати редактору гнучкість без втрати контролю над структурою. Згідно документації Wagtail, StreamField дозволяє створювати довільні типи контенту. У цьому матеріалі покажемо на реальних прикладах, як проєктувати, валідувати та підключати кастомні блоки до API.
Які проблеми вирішуємо
Звичайні RichTextBlock і ImageBlock не дозволяють контролювати структуру даних. За нашими даними, 80% помилок у контенті виникає через неструктуроване введення. Кастомні блоки фіксують структуру, валідують дані на стороні CMS і скорочують час правок на 40%. Крім того, вони дозволяють реалізувати бізнес-логіку, недоступну в стандартних блоках: наприклад, динамічне відображення блоків залежно від ролі користувача або A/B-тестування компонентів.
Як створити кастомний блок з вкладеними елементами?
Базовий елемент — клас, що наслідує від StructBlock. Ось приклад картки переваги та секції з картками:
from wagtail.blocks import StructBlock, CharBlock, RichTextBlock, ImageChooserBlock, ListBlock, ChoiceBlock
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:
template = 'blocks/feature_card.html'
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:
template = 'blocks/feature_section.html'
Шаблон feature_card.html отримує змінну value — словник з даними блока. Редактор може динамічно додавати та видаляти картки в секції без обмежень. Для глибокої вкладеності (наприклад, блоки всередині карток всередині секцій) налаштуйте шаблон форми в адмінці — це підвищує зручність редагування.
StreamField в моделі сторінки
Підключаємо блоки до моделі:
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)
content_panels = Page.content_panels + [FieldPanel('body')]Параметр use_json_field=True обов'язковий для Wagtail 3.0+. Дані зберігаються в JSONB-колонці PostgreSQL, що дозволяє робити запити через ORM. Це прискорює вибірку сторінок за вмістом блоків, наприклад, для пошуку.
Як реалізувати складну валідацію блоків?
Зауважте: коли простих перевірок (обов'язковість, довжина) недостатньо — перевизначте clean(). Наприклад, для блока з тарифами:
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Це дозволяє реалізувати бізнес-логіку будь-якої складності. Наші інженери з досвідом понад 5 років гарантують, що валідація працюватиме без збоїв, а редактор отримає зрозумілі підказки при заповненні форми.
Серіалізація кастомних блоків для API
Якщо використовуєте Wagtail як headless CMS, перевизначте get_api_representation():
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 Порівняння кастомних і стандартних блоків
| Критерій | Стандартні блоки | Кастомні StructBlock |
|---|---|---|
| Гнучкість структури | Тільки текст і медіа | Будь-яка модель даних |
| Валідація | Тільки обов'язковість | Повна бізнес-логіка |
| Шаблони | Вбудовані | Свої HTML/CSS |
| Швидкість розробки | Миттєво | 2-4 години на блок |
| Повторне використання | Тільки в одній моделі | У будь-яких сторінках |
Кастомні блоки окупаються вже на другому проєкті за рахунок повторного використання. Вони знижують кількість помилок у контенті на 60% і скорочують час приймального тестування вдвічі. Порівняно з балочними редакторами, кастомні блоки дають у 3 рази більше контролю над структурою.
Процес роботи та що входить
- Аналіз вимог — збираємо макети та контент-план.
- Проєктування — визначаємо типи полів і валідацію.
- Реалізація — пишемо класи блоків і шаблони (BEM, адаптив).
- Тестування — перевіряємо збереження, рендер, адаптивність.
- Деплой — викочуємо на staging і production.
У результаті ви отримуєте від 1 до 12 готових блоків з документацією. Також проводимо навчання редакторів. Усі блоки супроводжуються гарантією на 12 місяців. Зв'яжіться з нами для точної оцінки — ми підготуємо пропозицію під ваш проєкт.
Орієнтовні терміни
| Тип блока | Час розробки | Приклади |
|---|---|---|
| Простий (текст + зображення) | 2–4 години | Hero, FeatureCard |
| Середній (вкладені блоки) | 4–8 годин | FeatureSection, PricingBlock |
| Складний (з валідацією) | 8–16 годин | PricingBlock з бізнес-логікою |
Розробка набору з 8–12 блоків для корпоративного сайту — 3–5 робочих днів. Складні випадки обговорюються окремо. Замовте розробку кастомних блоків вже сьогодні та отримайте консультацію одного з наших провідних інженерів.
Типові помилки та як їх уникнути
| Помилка | Рішення |
|---|---|
| Занадто багато рівнів вкладеності | Обмежтеся 2–3 рівнями, інакше форма стає незручною |
Ігнорування use_json_field=True |
Використовуйте JSONB-колонку для продуктивності |
| Відсутність шаблону для блока | Завжди пишіть і тестуйте шаблон перед деплоєм |
| Перевантажена валідація | Давайте редактору зворотний зв'язок по мірі заповнення |
Ми гарантуємо, що після нашої розробки ви не зіткнетеся з цими проблемами.







