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







