Редакторы контента в 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-колонку для производительности |
| Отсутствие шаблона для блока | Всегда пишите и тестируйте шаблон перед деплоем |
| Перегруженная валидация | Давайте редактору обратную связь по мере заполнения |
Мы гарантируем, что после нашей разработки вы не столкнетесь с этими проблемами.







