Learn Wagtail CMS Course

How to Subclass Wagtail Pages

Subclassing is a darn nice feature in Object Orientated Programming. It lets you write re-useable code in several places; much like a function, but bigger.

The great thing about subclassing Page's in Wagtail is reusability; you can create one "master" or "parent" page and let other pages inherit from it (child pages). All the children will share common attributes from the parent page, but then you can extend each child to be unique by adding custom fields and methods.

The analogy I like to use is a real family.

You share personality traits with your mother and father. If you have siblings, they will have similar personality traits to all of you. All of these common personality traits come from your parents, that's where they begin. And as their offspring, you'll maintain a lot of traits throughout your life, but you'll also grow into your own person. When you grow into your own person you'll gain new perspective, skills and personality traits. Or in the land of OOP, you'll have new properties, methods and purposes.

Below is a copy and paste snippet of code you can use as a reference in your projects to immediately make 3 blog pages: one parent, and two child pages.

"""blog/models.py"""
from django import forms
from django.db import models

from modelcluster.fields import ParentalManyToManyField
from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel, InlinePanel
from wagtail.core.models import Page
from wagtail.images.edit_handlers import ImageChooserPanel


class BlogDetailPage(Page):
    """Parental blog detail page."""

    custom_title = models.CharField(
        max_length=100,
        blank=False,
        null=False,
        help_text="Overwrites the default title",
    )
    banner_image = models.ForeignKey(
        "wagtailimages.Image",
        blank=False,
        null=True,
        related_name="+",
        on_delete=models.SET_NULL,
    )

    categories = ParentalManyToManyField("blog.BlogCategory", blank=True)

    content_panels = Page.content_panels + [
        FieldPanel("custom_title"),
        ImageChooserPanel("banner_image"),
        MultiFieldPanel(
            [InlinePanel("blog_authors", label="Author", min_num=1, max_num=4)],
            heading="Author(s)",
        ),
        MultiFieldPanel(
            [FieldPanel("categories", widget=forms.CheckboxSelectMultiple)],
            heading="Categories",
        ),
    ]


# First subclassed blog post page
class ArticleBlogPage(BlogDetailPage):
    """A subclassed blog post page for articles."""

    template = "blog/article_blog_page.html"

    subtitle = models.CharField(max_length=100, blank=True, null=True)
    intro_image = models.ForeignKey(
        "wagtailimages.Image",
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        help_text="Best size for this image will be 1400x400",
    )

    content_panels = Page.content_panels + [
        # ...
        FieldPanel("subtitle"),
        ImageChooserPanel("intro_image"),
        # ...
    ]


# Second subclassed page
class VideoBlogPage(BlogDetailPage):
    """A video subclassed page."""

    template = "blog/video_blog_page.html"

    youtube_video_id = models.CharField(max_length=30)

    content_panels = Page.content_panels + [
        # ...
        FieldPanel("youtube_video_id"),
        # ...
    ]

The Git Commit