Customizing the default Wagtail admin form fields in a page model

The goal is to modify the default behavior of how the page slug is set in Wagtail so that we can define a custom slug value, which can then be used in a RoutablePageMixin to define dynamic routes.

Customizing the Wagtail Admin Form

In ./blog/admin_forms.py, create a class that inherits from WagtailAdminPageForm.

Note: Here, we are modifying the default behavior of the page slug field. By default, the slug field is populated based on the page title, but we will override this behavior to use a predefined value.

What is being changed:

We will make the slug field readonly, disabled, and not required. This ensures the slug is set programmatically and prevents admins from changing it.

from wagtail.admin.forms import WagtailAdminPageForm

class CustomAdminPageForm(WagtailAdminPageForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['slug'].widget.attrs['readonly'] = 'readonly'
        self.fields['slug'].disabled = True
        self.fields['slug'].required = False

Updating the Page Model

In ./blog/models.py import CustomAdminPageForm and define a custom save() method to apply the predefined slug value when the page is saved.

Next, assign the CustomAdminPageForm to the base_form_class of the Page model to use our custom form.

Note: When editing the page in the Wagtail admin and assigning a title, the slug field will initially be populated based on the title, this is the default behavior. When the page is saved, our custom slug will be applied.

from .admin_forms import CustomAdminPageForm

class BlogCategoriesPage(Page):
    # Require the BlogCategoriesPage to be a child of BlogIndexPage
    parent_page_types = ['blog.BlogIndexPage']

    # Assign our custom slug value when the page is saved
    def save(self, *args, **kwargs):
        self.slug = "categories" # Set predefined slug value
        super().save(*args, **kwargs)

    base_form_class = CustomAdminPageForm

Why manually set a slug on a Wagtail page model?

In my use case, I needed a way to have a predefined, uneditable slug (e.g., "categories") so I could reference it in a dynamic route using Wagtail’s @path decorator. This allows for a predictable URL structure.

For example, in the BlogIndexPage:

class BlogIndexPage(RoutablePageMixin, Page):
    # Create a dynamic route using our custom "categories" slug
    @path("categories/<str:category>/", name="category")
    def category(self, request, category):
        # Perform custom data queries and return context overrides to provide data to your dynamic route
        pass

If we hadn't set the custom slug "categories" in the BlogCategoriesPage, then the @path decorator would have required us to match the default slug generated by Wagtail (e.g., "blog-categories" if the title was "Blog Categories"). This would require the following route:

@path("blog-categories/<str:category/", name="category")

To summarize

  • Create a custom admin form class to prevent editing the slug that will be used in a dynamic route via the Wagtail @path decorator
  • Define a custom save function to set the Wagtail Page slug to our custom value
  • Use our predefined slug in a parent page (BlogIndexPage) to define a dynamic route

Using a Page model with a predefined slug makes it easy to reference in queries and include in sitemaps. Without a Page model for 'categories', this would have been more difficult to manage.

Helpful Links