Разработка кастомных Snippets Wagtail
Snippets в Wagtail — это модели Django, зарегистрированные в административной панели как управляемые объекты контента. В отличие от страниц, сниппеты не имеют URL и не участвуют в дереве сайта. Их используют для переиспользуемых фрагментов: меню, баннеров, контактных данных, команды, партнёров, отзывов — всего, что нужно в разных местах сайта независимо от структуры страниц.
Регистрация сниппета
# models.py
from django.db import models
from wagtail.snippets.models import register_snippet
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
from wagtail.fields import RichTextField
@register_snippet
class Testimonial(models.Model):
author_name = models.CharField('Имя', max_length=100)
author_title = models.CharField('Должность', max_length=100, blank=True)
author_photo = models.ForeignKey(
'wagtailimages.Image',
null=True, blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
text = RichTextField('Текст отзыва', features=['bold', 'italic'])
rating = models.PositiveSmallIntegerField('Оценка', default=5)
is_featured = models.BooleanField('Показывать на главной', default=False)
panels = [
MultiFieldPanel([
FieldPanel('author_name'),
FieldPanel('author_title'),
FieldPanel('author_photo'),
], heading='Автор'),
FieldPanel('text'),
FieldPanel('rating'),
FieldPanel('is_featured'),
]
def __str__(self):
return f'{self.author_name} — {self.rating}★'
class Meta:
verbose_name = 'Отзыв'
verbose_name_plural = 'Отзывы'
ordering = ['-is_featured', '-id']
ViewSetGroup для группировки в меню
Начиная с Wagtail 4.1 сниппеты регистрируются через SnippetViewSet — это даёт полный контроль над поведением в админке:
from wagtail.snippets.views.snippets import SnippetViewSet, SnippetViewSetGroup
from wagtail.snippets.models import register_snippet
class TestimonialViewSet(SnippetViewSet):
model = Testimonial
icon = 'comment'
menu_label = 'Отзывы'
menu_name = 'testimonials'
list_display = ['author_name', 'author_title', 'rating', 'is_featured']
list_filter = ['is_featured', 'rating']
search_fields = ['author_name', 'text']
ordering = ['-is_featured', '-id']
class TeamMemberViewSet(SnippetViewSet):
model = TeamMember
icon = 'user'
menu_label = 'Команда'
menu_name = 'team'
list_display = ['full_name', 'position', 'department']
list_filter = ['department']
class ContentSnippetsGroup(SnippetViewSetGroup):
menu_label = 'Контент'
menu_icon = 'folder-open-inverse'
menu_order = 300
items = [TestimonialViewSet, TeamMemberViewSet]
register_snippet(ContentSnippetsGroup)
Сниппет с историей версий
from wagtail.models import DraftStateMixin, RevisionMixin, PreviewableMixin
from wagtail.admin.panels import PublishingPanel
@register_snippet
class SiteAnnouncement(DraftStateMixin, RevisionMixin, PreviewableMixin, models.Model):
title = models.CharField(max_length=200)
body = RichTextField()
show_from = models.DateTimeField(null=True, blank=True)
show_until = models.DateTimeField(null=True, blank=True)
is_dismissible = models.BooleanField(default=True)
panels = [
FieldPanel('title'),
FieldPanel('body'),
FieldPanel('show_from'),
FieldPanel('show_until'),
FieldPanel('is_dismissible'),
PublishingPanel(),
]
def get_preview_template(self, request, mode_name):
return 'previews/announcement.html'
def __str__(self):
return self.title
class Meta:
verbose_name = 'Объявление'
verbose_name_plural = 'Объявления'
DraftStateMixin добавляет черновики и публикацию. RevisionMixin — историю версий с возможностью отката. PreviewableMixin — кнопку предпросмотра в редакторе.
Использование сниппетов в StreamField
from wagtail.snippets.blocks import SnippetChooserBlock
from wagtail.blocks import StructBlock, ListBlock
class TestimonialsBlock(StructBlock):
heading = CharBlock(max_length=100, required=False)
items = ListBlock(SnippetChooserBlock('content.Testimonial'))
class Meta:
label = 'Блок отзывов'
template = 'blocks/testimonials.html'
В шаблоне блока данные сниппета доступны напрямую:
{# blocks/testimonials.html #}
<section class="testimonials">
{% if value.heading %}<h2>{{ value.heading }}</h2>{% endif %}
<div class="testimonials__grid">
{% for item in value.items %}
{% include "snippets/testimonial_card.html" with testimonial=item %}
{% endfor %}
</div>
</section>
Сниппет как глобальные настройки
Частый паттерн — сниппет для настроек, которые редактор меняет редко, но которые нужны на всех страницах: контакты, соцсети, SEO-умолчания:
from wagtail.contrib.settings.models import BaseSiteSetting, register_setting
@register_setting
class SiteSettings(BaseSiteSetting):
phone = models.CharField('Телефон', max_length=30, blank=True)
email = models.EmailField('Email', blank=True)
address = models.TextField('Адрес', blank=True)
vk_url = models.URLField('ВКонтакте', blank=True)
telegram_url = models.URLField('Telegram', blank=True)
google_analytics_id = models.CharField('Google Analytics ID', max_length=20, blank=True)
panels = [
MultiFieldPanel([
FieldPanel('phone'),
FieldPanel('email'),
FieldPanel('address'),
], heading='Контакты'),
MultiFieldPanel([
FieldPanel('vk_url'),
FieldPanel('telegram_url'),
], heading='Соцсети'),
FieldPanel('google_analytics_id'),
]
class Meta:
verbose_name = 'Настройки сайта'
В шаблонах доступно через тег {% get_settings %}:
{% load wagtailsettings_tags %}
{% get_settings %}
<a href="tel:{{ settings.website.SiteSettings.phone }}">
{{ settings.website.SiteSettings.phone }}
</a>
API для headless
Если сайт использует Wagtail API, сниппеты нужно явно зарегистрировать в API:
# api.py
from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.api.v2.views import BaseAPIViewSet
from .models import Testimonial
api_router = WagtailAPIRouter('wagtailapi')
class TestimonialsAPIViewSet(BaseAPIViewSet):
model = Testimonial
body_fields = BaseAPIViewSet.body_fields + [
'author_name', 'author_title', 'text', 'rating',
]
listing_default_fields = BaseAPIViewSet.listing_default_fields + [
'author_name', 'rating', 'is_featured',
]
filter_fields = ['is_featured']
api_router.register_endpoint('testimonials', TestimonialsAPIViewSet)
Запрос вернёт структурированный JSON:
GET /api/v2/testimonials/?is_featured=true&order=-rating
Сроки
Один сниппет с панелями, поиском и фильтрацией — 2–3 часа. Комплект из 5–7 сниппетов для типичного корпоративного сайта (команда, партнёры, отзывы, настройки, меню, баннеры) с настройкой ViewSetGroup и API-доступом — 2–3 дня.







