Разработка сайта на CMS Wagtail

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка сайта на CMS Wagtail
Сложная
~2-4 недели
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка сайта на CMS Wagtail

Wagtail — Django CMS с богатым редактором Draftail, StreamField для гибкого контента и встроенным headless API. Используется крупными организациями: NASA, Google, Torchbox, Mozilla. Хорошо подходит для сложных editorial-проектов на Python-стеке.

Почему Wagtail, а не Django без CMS

Wagtail добавляет к Django:

  • Admin UI с live preview, revision history, workflow approval
  • StreamField — блочный редактор как Matrix в Craft
  • Routable Pages — кастомные URL внутри страницы без отдельных view
  • Images — встроенная обработка изображений с focal point
  • Documents — управление файлами
  • Snippets — переиспользуемые объекты не привязанные к странице
  • Search — полнотекстовый поиск через Elasticsearch или PostgreSQL

Архитектура

myproject/
├── myproject/
│   ├── settings/
│   │   ├── base.py
│   │   ├── dev.py
│   │   └── production.py
│   ├── urls.py
│   └── wsgi.py
├── home/              # стартовое приложение
├── blog/              # приложение блога
│   ├── models.py
│   ├── migrations/
│   └── templates/
│       └── blog/
├── core/              # общие компоненты
│   ├── models.py      # AbstractPage, Snippet-классы
│   └── blocks.py      # StreamField блоки
└── requirements/
    ├── base.txt
    └── production.txt

Установка и настройка

pip install wagtail
wagtail start myproject
cd myproject
python manage.py migrate
python manage.py createsuperuser
# settings/base.py — ключевые настройки
INSTALLED_APPS = [
    'home',
    'blog',
    'core',
    'search',
    'wagtail.contrib.routable_page',
    'wagtail.contrib.search_promotions',
    'wagtail.contrib.settings',
    'wagtail.embeds',
    'wagtail.sites',
    'wagtail.users',
    'wagtail.snippets',
    'wagtail.documents',
    'wagtail.images',
    'wagtail.search',
    'wagtail.admin',
    'wagtail',
    'modelcluster',
    'taggit',
    'django.contrib.admin',
    # ...
]

WAGTAIL_SITE_NAME = 'My Site'
WAGTAILIMAGES_IMAGE_MODEL = 'core.CustomImage'  # кастомная модель изображений
WAGTAILADMIN_BASE_URL = 'https://mysite.com'
WAGTAIL_ENABLE_WHATS_NEW_BANNER = False

Базовые Page Models

# blog/models.py
from django.db import models
from wagtail.models import Page, Orderable
from wagtail.fields import RichTextField, StreamField
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel
from wagtail.search import index
from wagtail.images.blocks import ImageChooserBlock
from wagtailmetadata.models import MetadataPageMixin
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from taggit.models import TaggedItemBase

class BlogIndexPage(Page):
    intro = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('intro'),
    ]

    subpage_types = ['blog.BlogPost']

    def get_context(self, request):
        context = super().get_context(request)
        posts = BlogPost.objects.live().public().order_by('-first_published_at')

        # Фильтрация по тегу
        tag = request.GET.get('tag')
        if tag:
            posts = posts.filter(tags__slug=tag)

        # Пагинация
        from django.core.paginator import Paginator
        paginator = Paginator(posts, 12)
        page = request.GET.get('page')
        context['posts'] = paginator.get_page(page)
        return context


class BlogPostTag(TaggedItemBase):
    content_object = ParentalKey(
        'BlogPost',
        related_name='tagged_items',
        on_delete=models.CASCADE,
    )


class BlogPost(MetadataPageMixin, Page):
    date = models.DateField('Post date')
    intro = models.CharField(max_length=250)
    body = StreamField([
        ('heading',       blocks.CharBlock(form_classname='title')),
        ('paragraph',     blocks.RichTextBlock()),
        ('image',         ImageChooserBlock()),
        ('quote',         blocks.BlockQuoteBlock()),
        ('embed',         EmbedBlock()),
        ('code',          CodeBlock()),
        ('call_to_action', CTABlock()),
    ], use_json_field=True)
    tags = ClusterTaggableManager(through=BlogPostTag, blank=True)
    categories = ParentalManyToManyField('blog.BlogCategory', blank=True)

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
        index.SearchField('body'),
        index.FilterField('date'),
    ]

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldPanel('date'),
            FieldPanel('tags'),
            FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
        ], heading='Blog information'),
        FieldPanel('intro'),
        FieldPanel('body'),
        InlinePanel('gallery_images', label='Gallery images'),
    ]

    parent_page_types = ['blog.BlogIndexPage']
    subpage_types = []

StreamField блоки

# core/blocks.py
from wagtail import blocks
from wagtail.images.blocks import ImageChooserBlock
from wagtail.embeds.blocks import EmbedBlock

class CTABlock(blocks.StructBlock):
    heading = blocks.CharBlock()
    text    = blocks.RichTextBlock(required=False)
    button_label = blocks.CharBlock()
    button_url   = blocks.URLBlock()
    variant = blocks.ChoiceBlock(choices=[
        ('primary',   'Primary'),
        ('secondary', 'Secondary'),
        ('outline',   'Outline'),
    ])

    class Meta:
        template = 'blocks/cta.html'
        icon     = 'pick'
        label    = 'Call to Action'


class TwoColumnBlock(blocks.StructBlock):
    left_column  = blocks.StreamBlock([
        ('paragraph', blocks.RichTextBlock()),
        ('image',     ImageChooserBlock()),
    ])
    right_column = blocks.StreamBlock([
        ('paragraph', blocks.RichTextBlock()),
        ('image',     ImageChooserBlock()),
    ])

    class Meta:
        template = 'blocks/two_column.html'
        icon     = 'grip'


class CodeBlock(blocks.StructBlock):
    language = blocks.ChoiceBlock(choices=[
        ('python',     'Python'),
        ('javascript', 'JavaScript'),
        ('bash',       'Bash'),
        ('sql',        'SQL'),
    ])
    code = blocks.TextBlock()

    class Meta:
        template = 'blocks/code.html'
        icon     = 'code'

Шаблоны

<!-- templates/blog/blog_post.html -->
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags %}

{% block content %}
<article>
    <header>
        <h1>{{ page.title }}</h1>
        <time datetime="{{ page.date|date:'Y-m-d' }}">
            {{ page.date|date:"d F Y" }}
        </time>
    </header>

    {% if page.header_image %}
        {% image page.header_image fill-1200x630-c100 as img %}
        <figure>
            <img src="{{ img.url }}" width="{{ img.width }}" height="{{ img.height }}" alt="{{ page.header_image.alt }}">
        </figure>
    {% endif %}

    <div class="post-body">
        {% for block in page.body %}
            {% include_block block %}
        {% endfor %}
    </div>

    <!-- Связанные посты -->
    {% with siblings=page.get_siblings.live.public.order_by('-first_published_at')[:3] %}
        {% if siblings %}
            <aside class="related">
                <h3>Читайте также</h3>
                {% for post in siblings %}
                    {% include "blog/_post_card.html" with post=post %}
                {% endfor %}
            </aside>
        {% endif %}
    {% endwith %}
</article>
{% endblock %}

Snippets — переиспользуемый контент

# core/models.py
from wagtail.snippets.models import register_snippet

@register_snippet
class Testimonial(models.Model):
    author_name = models.CharField(max_length=255)
    company     = models.CharField(max_length=255, blank=True)
    text        = models.TextField()
    avatar      = models.ForeignKey(
        'wagtailimages.Image',
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )

    panels = [
        FieldPanel('author_name'),
        FieldPanel('company'),
        FieldPanel('text'),
        FieldPanel('avatar'),
    ]

    def __str__(self):
        return f"{self.author_name} ({self.company})"

Wagtail API (Headless)

# settings/base.py
INSTALLED_APPS += ['wagtail.api.v2']

# urls.py
from wagtail.api.v2.views import PagesAPIViewSet
from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.images.api.v2.views import ImagesAPIViewSet
from wagtail.documents.api.v2.views import DocumentsAPIViewSet

api_router = WagtailAPIRouter('wagtailapi')
api_router.register_endpoint('pages',     PagesAPIViewSet)
api_router.register_endpoint('images',    ImagesAPIViewSet)
api_router.register_endpoint('documents', DocumentsAPIViewSet)

urlpatterns = [
    path('api/v2/', api_router.urls),
    # ...
]

Сроки разработки

Этап Время
Установка + настройка + деплой 1–2 дня
Page Models (5–7 типов) 3–5 дней
StreamField блоки (8–12 блоков) 2–4 дня
HTML-шаблоны 4–8 дней
Snippets и вспомогательный контент 1–2 дня
Поиск, теги, фильтрация 1–2 дня
Корпоративный сайт на Wagtail 15–25 дней