Розробка сайту на 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, історією редакцій, workflow затвердження
  • 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('Дата посту')
    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='Інформація про блог'),
        FieldPanel('intro'),
        FieldPanel('body'),
        InlinePanel('gallery_images', label='Зображення галереї'),
    ]

    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',   'Первинний'),
        ('secondary', 'Вторинний'),
        ('outline',   'Контур'),
    ])

    class Meta:
        template = 'blocks/cta.html'
        icon     = 'pick'
        label    = 'Заклик до дії'


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 днів