I am in the process of upgrading to Wagtail 2.16. One of the new features is a slim admin menu which I am sure many of my laptop users will really like - or would really like - if I had not just added a chunk of code that violates the last item in the exceptions list: MenuItem can no longer be sub-classed to customize its HTML output or load additional JavaScript
I had had an item that was restricted to be “one page of this type per site” and so it was easy to construct a menu item to display all the subpages that could be under that page - I just need to find the PersonIndexPage2 for the current site, and then create a url for the page explorer for that page.
class PeoplePages2MenuItems(MenuItem):
def __init__(self):
super().__init__(
label="People Pages",
url=None,
classnames="icon icon-user",
order=300,
)
def is_shown(self, request):
"""
The PeoplePages2MenuItem is only shown if there is a PersonPage2Template in the site.
"""
return PersonPage2Template.objects.in_site(Site.find_for_request(request)).exists()
def get_context(self, request):
"""
Constructs the url for listing PersonPage2 pages
"""
page = PersonIndexPage2.objects.descendant_of(Site.find_for_request(request).root_page).first()
self.url = reverse('wagtailadmin_explore', args=[page.id]) + "?ordering=title&people_pages_only=True"
return super().get_context(request)
@hooks.register('register_people_admin_menu_item')
def register_people_pages_v2_template_menu_item():
return PeoplePages2TemplateMenuItem()
But then someone asked me if they could add more than one
PersonIndexPage2 per site. So we will need more than one menu item for
“People Pages” - and we’ll need more than one link per site. So I had
a look at the MenuItem class and there is the render code, just
begging me to hijack it. so I removed the get_context
method above
and did all the dirty work in the render_html
method.
def render_html(self, request):
pages = PersonIndexPage2.objects.descendant_of(Site.find_for_request(request).root_page).all()
items = []
for page in pages:
context = self.get_context(request)
context['url'] = reverse('wagtailadmin_explore', args=[page.id]) + "?ordering=title&people_pages_only=True"
context['label'] = page.title
items.append(render_to_string(self.template, context, request=request))
return (' ').join(items)
That was great - for about 2 weeks. Then I started my Wagtail 2.16
upgrade and suddenly my “People Pages” links go to /admin/null
.
So I went poking around in the Wagtail source code and found what I
probably should have been using all the time. The Menu class has a
method menu_items_for_request
. This is where the is_shown
rules
are enforced - but more important for my current issue is the section
where it executes any hooks registered by a menu’s
construct_hook_name
. I have lots of code that uses hooks configured
with register_hook_name
but it hadn’t occurred to me to look for a
request-time equivalent.
So, first I need to define a construct hook:
class PeopleAdminMenu(Menu):
def __init__(self):
super().__init__(
register_hook_name='register_people_admin_menu_item',
construct_hook_name='construct_people_admin_menu_item',
)
Then I replaced my PeoplePages2MenuItems
class and the
register_people_admin_menu_item hook
that added it to the correct
top level menu item with a method to add the menu items.
@hooks.register('construct_people_admin_menu_item')
def add_people_pages2_menu_items(request, items):
site = Site.find_for_request(request)
if PersonPage2Template.objects.in_site(site).exists():
for page in PersonIndexPage2.objects.descendant_of(site.root_page).all():
pp2_menu_item = MenuItem(
page.title,
reverse('wagtailadmin_explore', args=[page.id]) + "?ordering=title&people_pages_only=True",
icon_name='icon icon-user',
order=300,
)
items.append(pp2_menu_item)
This contains all the same logic as the previous class. The if clause
contains the logic from the is_shown
method and the class’s init
parameters are combined with the dynamic url and label items from the
render_html method to instantiate a MenuItem. So much cleaner! I
should have been doing it like this all along.