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
slug
that will be used in a dynamic route via the Wagtail@path
decorator - Define a custom
save
function to set the WagtailPage
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.