Skip to content

Instantly share code, notes, and snippets.

@hazho
Forked from cnk/Field-level-permissions.md
Created July 20, 2020 18:07
Show Gist options
  • Save hazho/2fe8175e1debceec68cb252c1c4f10c5 to your computer and use it in GitHub Desktop.
Save hazho/2fe8175e1debceec68cb252c1c4f10c5 to your computer and use it in GitHub Desktop.

Restricting some fields in a model

We would like to be able to add images to events - or tag them with display locations such as "Home Page". But we do not want oridinary Event Planners to be able to do either of those things. Wagtail's admin interface is defined at the class level, so omitting those fields for just a subset of users would be hard to do in the panel definition. Instead we hide those fields during panel instantiation.

First, in the EventPage model, we create a MultiFieldPanel with a specific CSS class name:

MultiFieldPanel(
    [
        FieldPanel('display_locations', widget=forms.CheckboxSelectMultiple),
        StreamFieldPanel('assets'),
    ],
    heading='Admin-only Fields',
    # NOTE: The 'admin-only' class is how EventPage.get_edit_handler() identifies this MultiFieldPanel.
    classname='collapsible admin-only'
)

Then we override the get_edit_handler method to conditionally remove any panels with the class name 'admin-only':

@classmethod
def get_edit_handler(cls):
    """
    We override this method (which is added to the Page class in wagtail.admin.edit_handlers) in order to enforce
    our custom field-level permissions.
    """
    # Do the same thing that wagtail.admin.edit_handlers.get_edit_handler() would do...
    bound_handler = cls.edit_handler.bind_to_model(cls)
    # ... then enforce admin-only field permissions on the result.
    current_request = get_current_request()
    # This method gets called during certain manage.py commands, so we need to be able to gracefully fail if there
    # is no current request. Thus, if there is no current request, the admin-only fields are removed.
    if current_request is None or not current_request.user.has_perm('master_calendar.can_access_admin_fields'):
        # We know for sure that bound_handler.children[0].children is the list of Panels in the Content tab.
        # We must search through that list to find the admin-only MultiFieldPanel, and remove it.
        # The [:] gets us a copy of the list, so altering the original doesn't change what we're looping over.
        for child in bound_handler.children[0].children[:]:
            if 'admin-only' in child.classname:
                bound_handler.children[0].children.remove(child)
                break
    return bound_handler

We used a custom permission 'master_calendar.can_access_admin_fields' so we could give a handful of Event Planners the ability to add images or map items to Display Locations without making them full-fledged editors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment