Tutorial Summary

Almost every website has some form of navigation. Wagtail websites are no different. But creating a menu isn't as easy as making top level pages (although that's an option!). In this tutorial we're going to explore how to create a Menu System using a Clusterable Model, an Oderable, a Snippet, and a custom template tag.. from scratch!

Every good site has a navigation, and it's usually found in the header.

Wagtail already comes with an option to show pages in your menus, and all you have to do is query pages using PageName.objects.live().in_menu().

But that doesn't give you control over the placement of each page. So let's say you wanted a contact button to the very right of your menu, that's gets harder to do. You can drag and drop pages to have the proper order, and that would solve the ordering problem.

But what if you want to link to:

  • A certain part of a page, or
  • An external URL

Well then you're kind of stuck if you want either of those options. Also creating a mega menu would be pretty difficult.

Luckily Wagtail gives us all the ingredients we need to make our own custom menu system using:

  • 1 Clusterable Model
  • 1 Orderable Model
  • 1 Snippet
  • 1 Template Tag
  • and 1 loop in our template

Below you'll find the code that was used in the video.

Note: there are different files, and they all belong to an app called "menus".

"""menus/templatetags/menus_tags.py"""
from django import template

from ..models import Menu

register = template.Library()


@register.simple_tag()
def get_menu(slug):
    return Menu.objects.filter(slug=slug).first()
"""menus/models.py"""
from django.db import models

from django_extensions.db.fields import AutoSlugField
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from wagtail.admin.edit_handlers import (
    MultiFieldPanel,
    InlinePanel,
    FieldPanel,
    PageChooserPanel,
)
from wagtail.core.models import Orderable
from wagtail.snippets.models import register_snippet


class MenuItem(Orderable):

    link_title = models.CharField(
        blank=True,
        null=True,
        max_length=50
    )
    link_url = models.CharField(
        max_length=500,
        blank=True
    )
    link_page = models.ForeignKey(
        "wagtailcore.Page",
        null=True,
        blank=True,
        related_name="+",
        on_delete=models.CASCADE,
    )
    open_in_new_tab = models.BooleanField(default=False, blank=True)

    page = ParentalKey("Menu", related_name="menu_items")

    panels = [
        FieldPanel("link_title"),
        FieldPanel("link_url"),
        PageChooserPanel("link_page"),
        FieldPanel("open_in_new_tab"),
    ]

    @property
    def link(self):
        if self.link_page:
            return self.link_page.url
        elif self.link_url:
            return self.link_url
        return '#'

    @property
    def title(self):
        if self.link_page and not self.link_title:
            return self.link_page.title
        elif self.link_title:
            return self.link_title
        return 'Missing Title'


@register_snippet
class Menu(ClusterableModel):
    """The main menu clusterable model."""

    title = models.CharField(max_length=100)
    slug = AutoSlugField(populate_from="title", editable=True)
    # slug = models.SlugField()

    panels = [
        MultiFieldPanel([
            FieldPanel("title"),
            FieldPanel("slug"),
        ], heading="Menu"),
        InlinePanel("menu_items", label="Menu Item")
    ]

    def __str__(self):
        return self.title
{% load menus_tags %}
{# Your template; perhaps base.html #}

{% get_menu "main" as navigation %}

{% for item in navigation.menu_items.all %}
    <a href="{{ item.link }}" {% if item.open_in_new_tab %} target="_blank"{% endif %}>{{ item.title }}</a>
{% endfor %}
Wagtailmenus Repo

Near the end of this video I briefly talk about a package called wagtailmenus that basically does all of this for you (and a little more). Here are the link:

Sign up for our newsletter

Get notified about new lessons :)


Our Sites