Reputation: 1455
How can permissions be applied to individual fields of a Wagtail page? Let's say we have a page like this one:
class HomePage(Page):
body = RichTextField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('body', classname="full"),
]
Everyone should be allowed to edit the title - but only users with a certain permission should be able to alter the body.
Upvotes: 3
Views: 1112
Reputation: 5085
Wagtail do not support field based permission control. But you can achieve this by enabling/disabling fields
of the page's form. If you add a field to a page, you can pass whether that field should be enabled or disabled in the constructor. But how can you do that runtime?
Every HTML
page with a form in wagtail
is associated with a Django Form
. You can change the default form to a your own one like this.
from django.db import models
from wagtail.admin.forms.pages import WagtailAdminPageForm
from wagtail.core.models import Page
from wagtail.admin.edit_handlers import FieldPanel
class HomeForm(WagtailAdminPageForm):
pass
class HomePage(Page):
body = models.CharField(max_length=500, default='', blank=True)
content_panels = Page.content_panels + [
FieldPanel('body'),
]
base_form_class = HomeForm # Tell wagtail to user our form
This way, every time you load the create view or edit view of HomePage
, wagtail will create an instance from HomeForm
class. Now what you have to do is, to check the user status and enable/disable the required field when creating an instance of the HomeForm
.
For this example, I will enable the body
field only when the user is a superuser
.
class HomeForm(WagtailAdminPageForm):
# Override the constructor to do the things at object creation.
def __init__(self, data=None, files=None, parent_page=None, subscription=None, *args, **kwargs):
super().__init__(data, files, parent_page, subscription, *args, **kwargs)
user = kwargs['for_user'] # Get the user accessing the form
is_superuser = user.is_superuser
body = self.fields.get('body') # Get the body field
body.disabled = not is_superuser # Disable the body field if user is not a superuser
This way, every time a non superuser loads the create or edit page, the body
field will be disabled.
But if you want to remove access only from edit page, you need to use self.initial
variable. This variable is a dictionary with initial values to be used when showing the form.
You can check the value of a required field (like title
) from self.initial
and if the field's value is empty, that means the create page is loaded and if there is a value, that means the edit page is loaded.
Upvotes: 2
Reputation: 9131
I realize this is a really old question now, but just in case people come across it in the future, here's how my code shop does this in Wagtail 2.3. It may or may not work in later versions.
Add the following to the Page subclass you've written:
panels = [
...
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'
),
...
]
class Meta:
verbose_name = 'Event'
verbose_name_plural = 'Events'
ordering = ['start_date', 'title']
permissions = (
('can_access_admin_fields', 'Can access Event Admin fields'),
)
@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('calendar_app.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
This is, obviously, quite funky and fragile. But it's the only solution I could find.
Upvotes: 4