Разработка кастомных Page Models Wagtail

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомных Page Models Wagtail
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1230
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1167
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    863
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1077
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    829
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    844

Разработка кастомных Page Models Wagtail

Page Models — сердце Wagtail. Каждый тип страницы — это Django-модель, наследующая от Page. Тип определяет поля, редактор в Admin, шаблон и маршруты.

Базовая структура Page Model

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, TabbedInterface, ObjectList
from wagtail.search import index
from modelcluster.fields import ParentalKey

class ServicePage(Page):
    # Поля
    intro    = models.CharField(max_length=500, blank=True)
    body     = RichTextField(blank=True)
    icon     = models.ForeignKey(
        'wagtailimages.Image',
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    price_from = models.DecimalField(max_digits=10, decimal_places=0, null=True, blank=True)

    # Индексация для поиска
    search_fields = Page.search_fields + [
        index.SearchField('intro'),
        index.SearchField('body'),
        index.FilterField('price_from'),
    ]

    # Интерфейс редактора — с вкладками
    content_panels = Page.content_panels + [
        FieldPanel('intro'),
        FieldPanel('icon'),
        FieldPanel('body'),
        InlinePanel('features', label='Особенности'),
    ]

    promote_panels = [
        MultiFieldPanel([
            FieldPanel('slug'),
            FieldPanel('seo_title'),
            FieldPanel('search_description'),
        ], heading='SEO'),
    ]

    pricing_panels = [
        FieldPanel('price_from'),
        InlinePanel('pricing_tiers', label='Тарифы'),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Содержание'),
        ObjectList(pricing_panels, heading='Цены'),
        ObjectList(promote_panels, heading='SEO'),
    ])

    # Ограничение иерархии
    parent_page_types = ['services.ServicesIndexPage']
    subpage_types     = []

    class Meta:
        verbose_name = 'Service Page'


class ServiceFeature(Orderable):
    page  = ParentalKey(ServicePage, on_delete=models.CASCADE, related_name='features')
    title = models.CharField(max_length=255)
    text  = models.TextField(blank=True)
    icon  = models.CharField(max_length=50, blank=True)  # icon name

    panels = [
        FieldPanel('title'),
        FieldPanel('text'),
        FieldPanel('icon'),
    ]

RoutablePage — несколько URL у одной страницы

from wagtail.contrib.routable_page.models import RoutablePage, path, re_path

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

    content_panels = Page.content_panels + [FieldPanel('intro')]
    subpage_types  = ['blog.BlogPost']

    @path('')
    def index_view(self, request):
        posts = BlogPost.objects.live().child_of(self).order_by('-first_published_at')

        from django.core.paginator import Paginator
        paginator = Paginator(posts, 12)
        return self.render(request, context_overrides={
            'posts': paginator.get_page(request.GET.get('page')),
        })

    @path('category/<str:category_slug>/')
    def category_view(self, request, category_slug):
        category = get_object_or_404(BlogCategory, slug=category_slug)
        posts = BlogPost.objects.live().child_of(self).filter(categories=category)
        return self.render(request, context_overrides={
            'posts':    posts,
            'category': category,
        }, template='blog/blog_category.html')

    @path('tag/<str:tag>/')
    def tag_view(self, request, tag):
        posts = BlogPost.objects.live().child_of(self).filter(tags__slug=tag)
        return self.render(request, context_overrides={
            'posts': posts,
            'tag':   tag,
        })

    @re_path(r'^archive/(\d{4})/$')
    def year_archive(self, request, year):
        posts = BlogPost.objects.live().child_of(self).filter(date__year=year)
        return self.render(request, context_overrides={
            'posts': posts,
            'year':  year,
        })

Page с кастомными изображениями

# core/models.py
from wagtail.images.models import AbstractImage, AbstractRendition, Image

class CustomImage(AbstractImage):
    alt_text = models.CharField(max_length=255, blank=True)
    credit   = models.CharField(max_length=255, blank=True)

    admin_form_fields = Image.admin_form_fields + [
        'alt_text',
        'credit',
    ]

    @property
    def default_alt_text(self):
        return self.alt_text or self.title


class CustomRendition(AbstractRendition):
    image = models.ForeignKey(CustomImage, on_delete=models.CASCADE, related_name='renditions')

    class Meta:
        unique_together = [('image', 'filter_spec', 'focal_point_key')]

get_context — передача данных в шаблон

def get_context(self, request):
    context = super().get_context(request)

    # Дополнительные данные
    context['related_posts'] = BlogPost.objects.live().exclude(
        pk=self.pk
    ).filter(
        categories__in=self.categories.all()
    ).distinct().order_by('-first_published_at')[:3]

    # Форма комментариев
    context['comment_form'] = CommentForm()

    # Данные из GET-параметров
    context['current_tag'] = request.GET.get('tag')

    return context

Ревизии и черновики

Page Models автоматически поддерживают ревизии и workflow. Для включения требуется только RevisionMixin (уже в базовом Page). Настройка количества хранимых ревизий:

WAGTAIL_WORKFLOW_MAX_LOCK_EXPIRY_DAYS = 14

# В модели
class BlogPost(Page):
    MAX_NUM_REVIEWERS = 3

Разработка 5–7 Page Models с RoutablePage и InlinePanel — 3–6 дней.