Customizing the default Wagtail admin form fields in a page model
by bfarrell on Dec 13, 2024
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
slugthat will be used in a dynamic route via the Wagtail@pathdecorator - Define a custom
savefunction to set the WagtailPageslugto our custom value - Use our predefined
slugin 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.