Published on 14 February 2015

Simple orderable menus for Wagtail

The last few months I've been doing a good amount of work using Wagtail. Wagtail is a pretty cool CMS built on top of the Django framework. It hasn't been around for long but it has a pretty active community and a nice documentation. I've built a couple of relatively large sites with it and been quite happy with the result so far.

If you're new to Wagtail this demo is a great starting point. I highly recommend playing around for a bit and checking some of the cool features Wagtail ships with. One of these features are the so-called Snippets. Snippets are basically simple Django models that can be edited through a default Wagtail admin interface. They're a very handy tool to build something like a simple menu.

In your models.py you probably already have a LinkFields class as it comes with the Wagtail demo:

class LinkFields(models.Model):
    """
    Represents a link to an external page, a document or a Wagtail page
    """
    link_external = models.URLField(
        "External link",
        blank=True,
        null=True,
        help_text='Set an external link if you want the link to point somewhere outside the CMS.'
    )
    link_page = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        on_delete=models.SET_NULL,
        blank=True,
        related_name='+',
        help_text='Choose an existing page if you want the link to point somewhere inside the CMS.'
    )
    link_document = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        on_delete=models.SET_NULL,
        blank=True,
        related_name='+',
        help_text='Choose an existing document if you want the link to open a document.'
    )

Define a class inheriting from LinkFields to represent your menu items:

class MenuItem(LinkFields):
    @property
    def url(self):
        return self.link

    def __unicode__(self):
        if self.link_external:
            title = self.link_external
        elif self.link_page:
            title = self.link_page.title
        elif self.link_document:
            title = self.link_document.title
        return title

    class Meta:
        verbose_name = "Menu item"
        description = "Items appearing in the menu"

* Feel free to add extra fields such as title, CSS class etc.. if you need them :)

And finally add the Menu class, the MenuManager class and another class to represent the relationship between a Menu and an item (MenuMenuItem)

class MenuMenuItem(Orderable, MenuItem):
    parent = ParentalKey(to='yourapp.Menu', related_name='menu_items')
    
class MenuManager(models.Manager):
    def get_by_natural_key(self, name):
        return self.get(menu_name=name)

@register_snippet
class Menu(models.Model):
    objects = MenuManager()
    menu_name = models.CharField(max_length=255, null=False, blank=False)

    @property
    def items(self):
        return self.menu_items.all()

    def __unicode__(self):
        return self.menu_name

    class Meta:
        verbose_name = "Navigation menu"
        description = "Navigation menu"

Menu.panels = [
    FieldPanel('menu_name', classname='full title'),
    InlinePanel(Menu, 'menu_items', label="Menu Items", help_text='Set the menu items for the current menu.')
]

Don't forget to add the @register_snippet decorator!

If everything is fine you should end up with something like :

Wagtail menu

Enjoy!