Редактори контенту в 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-колонку для продуктивності |
| Відсутність шаблону для блока | Завжди пишіть і тестуйте шаблон перед деплоєм |
| Перевантажена валідація | Давайте редактору зворотний зв'язок по мірі заповнення |
Ми гарантуємо, що після нашої розробки ви не зіткнетеся з цими проблемами.







