Skip to content

Custom buttons and views for objects in ModelAdmin #25

@donhauser

Description

@donhauser

Is your proposal related to a problem?

Example:
Imagine you have a model like "Organisation" consisting of attributes like name, employees and so on and you want to create multiple views for it (through the admin panel), e.g. a "list of employees", "organisation overview".
Now, since you are using ModelAdmin, you would like to have a "employee list" and "overview" button beneath each organisation object in the index view, so you can easily access both object-views.

Note: You could use inspect_view for one action, but not for multiple actions.

Currently it is quite tricky implement new action-buttons with separate views because:

  • Adding further buttons is not described directly, the documentation only hints at ButtonHelper
  • Registering new model action-urls is undocumented
  • Far too many steps needed:
    • Create a super class of ButtonHelper
    • Add your button generating function to button ButtonHelper (like delete_button())
    • Overload ButtonHelper.get_buttons_for_obj() and append your button to the existing ones
    • Create a custom view function in your ModelAdmin class (like delete_view())
    • Overload ModelAdmin.get_admin_urls_for_registration() and append your custom view with the action url
    • specify button_helper_class=.. in your ModelAdmin

This problem is also discussed in #7 and #10

Describe the solution you'd like

In contrast to #7 and #10, I am proposing a very minimal change in ButtonHelper and ModelAdmin only.
The goal is to simplify the steps above without breaking any backwards compatibility in Wagtail, so the feature is lightweight but effective:

  • By removing the need to overload the two method above, a lot of code is saved and the complexity is reduced by a lot
  • The button generating functions (add_button(), edit_button(), ..) are all almost the same and can be defaulted for custom buttons
  • Simple custom buttons should be definable in ButtonHelper with a simple data structure (e.g. a list containing the action, label, title, ..)
  • The views (with their actions) should be definable in ModelAdmin using a simpler structure as well

A scratch code can be seen below.

Describe alternatives you've considered

One alternative would be to rewrite ButtonHelper as mentioned in #10.
By changing ButtonHelper heavily, old Wagtail-Projects might be broken, so this is rather a long term goal.

Additional context

Changes to Wagtail:

# Changes to ButtonHelper
class ButtonHelper:
    
    # Default button generator for custom/extra buttons (structured like add_button)
    def extra_button(self, url=None, label='', title=None, classnames_add=None, classnames_exclude=None):
        if classnames_add is None:
            classnames_add = []
        if classnames_exclude is None:
            classnames_exclude = []

        cn = self.finalise_classname(classnames_add, classnames_exclude)
        
        if not title:
            title = label
        
        return {
            'url': url,
            'label': label,
            'classname': cn,
            'title': title,
        }
    
    def get_buttons_for_obj(self, obj, exclude=None, classnames_add=None,
                            classnames_exclude=None):
        
        # OLD CODE HERE
       
        # New code

        # Check if the custom data structure is present
        if hasattr(self, "custom_object_buttons"):
             button_list = self.custom_object_buttons

            for action, kw in button_list:
            
                # TODO unite kw['classnames_add'] and classnames_add
                # TODO unite kw['classnames_exclude'] and classnames_exclude

                # create and append the button using the regular url pattern
                button = self.extra_button(self.url_helper.get_action_url(action, pk), **kw)
                btns.append(button)
        
        return btns

class ModelAdmin:

    def get_admin_urls_for_registration(self):        
        
        # OLD CODE HERE
       
        # New code
        # create url pattern for each custom view, just like for "add", "edit", ...
        urls = urls + tuple(
            re_path(
                self.url_helper.get_action_url_pattern(action),
                view,
                name=self.url_helper.get_action_url_name(action))
            for action, view in self.get_custom_object_views()
        )
        
        return urls

    # fallback method
    def get_custom_object_views(self):
        return []

Adding Buttons would now be much easier:

class MyButtonHelper(ButtonHelper):
    
    # custom definitions
    # (action, attributes)
    custom_object_buttons = [
        ("empolyees", {"label": 'Employee List', "add_class":["some_class"]}),
        ("overview", {"label": 'Overview', "title": 'Show a detailed overview table'}),
        ..
    ]


class MyModelWagtailAdmin(ModelAdmin):

    button_helper_class = MyButtonHelper

    def empolyees_view(self, request, instance_pk):
        # some call to View.as_view(..)

    def overview_view(self, request, instance_pk):
        # some call to OtherView.as_view(..)

    # Define custom object views
    #   This is a function because self is needed
    #   It would be even nicer to have only a list of actions
    #   and to automatically return self.{action}_view
    def get_custom_object_views(self):
        return [
            # (action, view)
            ("empolyees", self.empolyees_view),
            ("overview", self.overview_view),
        ]

If you like this suggestion, I will create a pull request for this feature

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions