Tutorial Summary

Discover how to use Djangos Inline Models within a Wagtail Page in a feature called Orderable. Orderables let you add movable content to your page without needing a StreamField. In this video, we'll create a Bootstrap 4 Image Gallery on our Home Page model using an Orderable.

In this video I go over how to add an Orderable to an existing Wagtail CMS Page. We'll use this feature instead of a StreamField to create an image gallery using a Bootstrap 4 Carousel.

The pros to using an Orderable instead of a ListBlock StreamField is that you can set a minimum and maximum number of items to add. In our example, we set a maximum of 5 images and a minimum of 1.

But the other hidden benefit is we can place our new Orderable content anywhere in the template and it's not confined to the loop where our StreamFields are rendered.

# home/models.py
from django.db import models

from modelcluster.fields import ParentalKey

from wagtail.admin.edit_handlers import (
    FieldPanel,
    MultiFieldPanel,
    InlinePanel,
    StreamFieldPanel,
    PageChooserPanel,
)
from wagtail.core.models import Page, Orderable
from wagtail.core.fields import RichTextField, StreamField
from wagtail.images.edit_handlers import ImageChooserPanel

from streams import blocks


class HomePageCarouselImages(Orderable):
    """Between 1 and 5 images for the home page carousel."""

    page = ParentalKey("home.HomePage", related_name="carousel_images")
    carousel_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=False,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    panels = [ImageChooserPanel("carousel_image")]


class HomePage(Page):
    """Home page model."""

    template = "home/home_page.html"
    max_count = 1

    banner_title = models.CharField(max_length=100, blank=False, null=True)
    banner_subtitle = RichTextField(features=["bold", "italic"])
    banner_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=False,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    banner_cta = models.ForeignKey(
        "wagtailcore.Page",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    content = StreamField([("cta", blocks.CTABlock())], null=True, blank=True)

    content_panels = Page.content_panels + [
        MultiFieldPanel(
            [
                FieldPanel("banner_title"),
                FieldPanel("banner_subtitle"),
                ImageChooserPanel("banner_image"),
                PageChooserPanel("banner_cta"),
            ],
            heading="Banner Options",
        ),
        MultiFieldPanel(
            [InlinePanel("carousel_images", max_num=5, min_num=1, label="Image")],
            heading="Carousel Images",
        ),
        StreamFieldPanel("content"),
    ]

    class Meta:

        verbose_name = "Home Page"
        verbose_name_plural = "Home Pages"
{# templates/home/home_page.html #}
{% extends "base.html" %}

{% load wagtailcore_tags wagtailimages_tags %}

{% block content %}

    {% image self.banner_image width-3560 as img %}

    <div class="jumbotron" style="background-image: url('{{ img.url }}'); min-height: 400px; height: 40vh; background-size: cover; background-position: center top; display: flex; flex-direction: column; justify-content: center;">
        <h1 class="display-4">{{ self.banner_title }}</h1>
        <div class="lead">{{ self.banner_subtitle|richtext }}</div>
        {% if self.banner_cta %}
            <a class="btn btn-primary btn-lg" href="#" role="button">@todo</a>
        {% endif %}
    </div>

    {# Example of an Orderable from home/models.py #}
    <div id="carouselExampleControls" class="carousel slide" data-ride="carousel">
        <div class="carousel-inner">
            {% for loop_cycle in self.carousel_images.all %}
                {% image loop_cycle.carousel_image fill-900x400 as img %}
                <div class="carousel-item{% if forloop.counter == 1 %} active{% endif %}">
                    <img src="{{ img.url }}" class="d-block w-100" alt="{{ img.alt }}">
                </div>
            {% endfor %}
        </div>
        <a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev">
            <span class="carousel-control-prev-icon" aria-hidden="true"></span>
            <span class="sr-only">Previous</span>
        </a>
        <a class="carousel-control-next" href="#carouselExampleControls" role="button" data-slide="next">
            <span class="carousel-control-next-icon" aria-hidden="true"></span>
            <span class="sr-only">Next</span>
        </a>
    </div>

    {% for block in page.content %}
        {% include_block block %}
    {% endfor %}
{% endblock %}
Sign up for our newsletter

Get notified about new lessons :)


Our Sites