Multiple ModelAdmins for one Wagtail model

Question:

Let’s say I have a model:

class BlogPost(Page):

date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = StreamField([
    ('paragraph', blocks.RichTextBlock()),
    ('image', ImageChooserBlock()),
    ('gallery', CarouselBlock()),
    ('video', EmbedBlock()),
])
...

And I would like to create multiple pages in the sidebar that relate to this model. I tried an approach like this:

    class BlogPostAdmin(ModelAdmin):
        model = BlogPost
        ...

    class DraftPostAdmin(ModelAdmin):
        model = BlogPost
        #query for drafts
        ...

    class ScheduledPostAdmin(ModelAdmin):
        model = BlogPost
        #query for scheduled posts
        ...

    class BlogGroup(ModelAdminGroup):
        menu_label = 'Blog'
        items = (BookAdmin, AuthorAdmin, GenreAdmin)
        ...

    modeladmin_register(BlogGroup)

But the issue is that all of the pages show model instances that match the queryset for the first ModelAdmin. What is the best way to go about implementing multiple menu items to manage different aspects of one model in Wagtail?

Asked By: gsundberg

||

Answers:

Use a proxy model and then define an appropriate manager for each proxy model. I have this working in an existing Wagtail-based application where I define proxy models for various states of Memberships in a membership application. In my case the base model is Member, but then I have CurrentMember, NonCurrentMember, etc. This comment and related discussion might also be of interest.

Answered By: Dan Swain

Here is a slightly different approach, by overriding some of the methods on your BlogGroup and using only a single BlogPostAdmin you can get pretty far.

Code

from wagtail.admin.menu import MenuItem

from wagtail.contrib.modeladmin.options import (
    ModelAdmin, ModelAdminGroup, modeladmin_register)

from bakerydemo.blog.models import BlogPage

class ModelAdminQueryMenuItem(MenuItem):
    # based on the `ModelAdminMenuItem` but extends the Wagtail Admin `MenuItem` directly.

    def __init__(self, model_admin, order, query, label_append=''):
        self.model_admin = model_admin
        url = model_admin.url_helper.index_url + "?" + query
        classnames = 'icon icon-%s' % model_admin.get_menu_icon()
        super().__init__(
            label=model_admin.get_menu_label() + label_append,
            url=url,
            classnames=classnames,
            order=order
        )

    def is_shown(self, request):
        return self.model_admin.permission_helper.user_can_list(request.user)


class BlogPageAdmin(ModelAdmin):
    model = BlogPage

    def get_menu_items(self, order=None):
        # new class method that builds a list of menu_item(s)
        menu_items = []

        ## build 'live only' (no unpublished changes) items
        live_menu_item = ModelAdminQueryMenuItem(self, order or self.get_menu_order(), query='has_unpublished_changes=False', label_append=' (Live)')
        menu_items.append(live_menu_item)

        ## build 'draft' items
        draft_menu_item = ModelAdminQueryMenuItem(self, order or self.get_menu_order(), query='has_unpublished_changes=True', label_append=' (Draft)')
        menu_items.append(draft_menu_item)

        return menu_items

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        ## read the request and modify the qs as needed if query param does not work easily
        return qs

class BlogGroup(ModelAdminGroup):
    menu_label = 'Blog'
    items = (BlogPageAdmin, )

    def get_submenu_items(self):
        menu_items = []
        item_order = 1
        for modeladmin in self.modeladmin_instances:
            menu_items = menu_items + modeladmin.get_menu_items(item_order)
            item_order = len(menu_items) + 1
        return menu_items

modeladmin_register(BlogGroup)

Explanation

  • Set up a single BlogPageAdmin class, this will have a new method on it get_menu_items (plural), which will return a list of Wagtail Menu Items.
  • We generate these menu items by overriding the Wagtail admin class MenuItem but we also add a user permissions helper, see the menus.py helper file within modeladmin. This provides us a way to customise the URL (adding url params) and the label, this can be modified as needed to get our sub-menu items presenting the way we want.
  • get_menu_items manually builds up ALL the menu items we want, we can customise anything here, including label and icon to show what you want, essentially though this just provides a way to pass in a custom query params string to the url that gets built up.
  • The query param can be anything we want, a search string and many of the fields on Page can be used without any code changes as they just get passed in as a Dict into our query. Below I have put all the possible values.
  • For anything else more specific, we will need to override the method get_queryset and read the request params and update our query accordingly.
  • You will need to customise the View’s page title but this can be done by reading the request in the view template customisation or by further customising the url that gets built up.
  • Finally, in our BlogGroup we override the get_submenu_items to use the custom method get_menu_items. Note that this assumes EACH model passed in to this group will have that method.

Query String Fields

  • Choices are: blog_person_relationship, body, content_type, content_type_id, date_published, depth, draft_title, expire_at, expired, first_published_at, formsubmission, go_live_at, group_permissions, has_unpublished_changes, id, image, image_id, introduction, last_published_at, latest_revision_created_at, live, live_revision, live_revision_id, locked, numchild, owner, owner_id, page_ptr, page_ptr_id, path, redirect, revisions, search_description, searchpromotion, seo_title, show_in_menus, sites_rooted_here, slug, subtitle, tagged_items, tags, title, url_path, view_restrictions
Answered By: LB Ben Johnston

Agree that ProxyModels are the most obvious route…

ModelAdmin.get_queryset() can filter the listview and it’s referenced in some of the answers. adding a method like this to your modelAdmin

class DraftPostAdmin(ModelAdmin):
    model = BlogPost

    def get_queryset(self, request): 
        return super().get_queryset().filter(published_status='Draft')
Answered By: MSully
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.