kopia lustrzana https://github.com/wagtail/wagtail
Add ability to check permission on parent `PanelGroup` class
- Used by TabbedInterface, ObjectList, FieldRowPanel, MultiFieldPanelpull/9414/head
rodzic
55f42d29c8
commit
19fd2ceb98
|
@ -54,6 +54,7 @@ Changelog
|
|||
* The `image_url` template tag, when using the serve view to redirect rather than serve directly, will now use temporary redirects with a cache header instead of permanent redirects (Jake Howard)
|
||||
* Add new test assertions to `WagtailPageTestCase` - `assertPageIsRoutable`, `assertPageIsRenderable`, `assertPageIsEditable`, `assertPageIsPreviewable` (Andy Babic)
|
||||
* Add documentation to the performance section about how to better create image URLs when not used directly on the page (Jake Howard)
|
||||
* Add ability to provide a required `permission` to `PanelGroup`, used by `TabbedInterface`, `ObjectList`, `FieldRowPanel` and `MultiFieldPanel` (Oliver Parker)
|
||||
* Fix: Prevent `PageQuerySet.not_public` from returning all pages when no page restrictions exist (Mehrdad Moradizadeh)
|
||||
* Fix: Ensure that duplicate block ids are unique when duplicating stream blocks in the page editor (Joshua Munn)
|
||||
* Fix: Revise colour usage so that privacy & locked indicators can be seen in Windows High Contrast mode (LB (Ben Johnston))
|
||||
|
|
|
@ -65,6 +65,7 @@ See [](/reference/pages/panels) for the set of panel types provided by Wagtail.
|
|||
A view performs the following steps to render a model form through the panels mechanism:
|
||||
|
||||
- The top-level panel object for the model is retrieved. Usually this is done by looking up the model's `edit_handler` property and falling back on an `ObjectList` consisting of children given by the model's `panels` property. However, it may come from elsewhere - for example, the ModelAdmin module allows defining it on the ModelAdmin configuration object.
|
||||
- If the `PanelsGroup`s permissions do not allow a user to see this panel, then nothing more will be done.
|
||||
- The view calls `bind_to_model` on the top-level panel, passing the model class, and this returns a clone of the panel with a `model` property. As part of this process the `on_model_bound` method is invoked on each child panel, to allow it to perform additional initialisation that requires access to the model (for example, this is where `FieldPanel` retrieves the model field definition).
|
||||
- The view then calls `get_form_class` on the top-level panel to retrieve a ModelForm subclass that can be used to edit the model. This proceeds as follows:
|
||||
- Retrieve a base form class from the model's `base_form_class` property, falling back on `wagtail.admin.forms.WagtailAdminModelForm`
|
||||
|
|
|
@ -77,13 +77,18 @@ Here are some Wagtail-specific types that you might include as fields in your mo
|
|||
|
||||
A ``list`` or ``tuple`` of child panels
|
||||
|
||||
.. attribute:: MultiFieldPanel.heading
|
||||
.. attribute:: MultiFieldPanel.heading (optional)
|
||||
|
||||
A heading for the fields
|
||||
|
||||
.. attribute:: MultiFieldPanel.help_text
|
||||
.. attribute:: MultiFieldPanel.help_text (optional)
|
||||
|
||||
Help text to be displayed against the panel.
|
||||
|
||||
.. attribute:: MultiFieldPanel.permission (optional)
|
||||
Allows a panel to be selectively shown to users with sufficient permission. Accepts a permission codename such as ``'myapp.change_blog_category'`` - if the logged-in user does not have that permission, the field will be omitted from the form.
|
||||
Similar to `FieldPanel.permission`
|
||||
The panel group will not be visible if the permission check does not pass.
|
||||
```
|
||||
|
||||
### InlinePanel
|
||||
|
@ -103,7 +108,7 @@ Note that you can use `classname="collapsed"` to load the panel collapsed under
|
|||
### FieldRowPanel
|
||||
|
||||
```{eval-rst}
|
||||
.. class:: FieldRowPanel(children, classname=None)
|
||||
.. class:: FieldRowPanel(children, classname=None, permission=None)
|
||||
|
||||
This panel creates a columnar layout in the editing interface, where each of the child Panels appears alongside each other rather than below.
|
||||
|
||||
|
@ -113,13 +118,18 @@ Note that you can use `classname="collapsed"` to load the panel collapsed under
|
|||
|
||||
A ``list`` or ``tuple`` of child panels to display on the row
|
||||
|
||||
.. attribute:: FieldRowPanel.classname
|
||||
.. attribute:: FieldRowPanel.classname (optional)
|
||||
|
||||
A class to apply to the FieldRowPanel as a whole
|
||||
|
||||
.. attribute:: FieldRowPanel.help_text
|
||||
.. attribute:: FieldRowPanel.help_text (optional)
|
||||
|
||||
Help text to be displayed against the panel.
|
||||
|
||||
.. attribute:: FieldRowPanel.permission (optional)
|
||||
Allows a panel to be selectively shown to users with sufficient permission. Accepts a permission codename such as ``'myapp.change_blog_category'`` - if the logged-in user does not have that permission, the field will be omitted from the form.
|
||||
Similar to `FieldPanel.permission`
|
||||
The panel group will not be visible if the permission check does not pass.
|
||||
```
|
||||
|
||||
### HelpPanel
|
||||
|
|
|
@ -97,6 +97,7 @@ There are multiple improvements to the documentation theme this release, here ar
|
|||
* The `image_url` template tag, when using the serve view to redirect rather than serve directly, will now use temporary redirects with a cache header instead of permanent redirects (Jake Howard)
|
||||
* Add new test assertions to `WagtailPageTestCase` - `assertPageIsRoutable`, `assertPageIsRenderable`, `assertPageIsEditable`, `assertPageIsPreviewable` (Andy Babic)
|
||||
* Add documentation to the performance section about how to better create image URLs when not used directly on the page (Jake Howard)
|
||||
* Add ability to provide a required `permission` to `PanelGroup`, used by `TabbedInterface`, `ObjectList`, `FieldRowPanel` and `MultiFieldPanel` (Oliver Parker)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
|
|
@ -430,12 +430,15 @@ class PanelGroup(Panel):
|
|||
"""
|
||||
|
||||
def __init__(self, children=(), *args, **kwargs):
|
||||
permission = kwargs.pop("permission", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.children = children
|
||||
self.permission = permission
|
||||
|
||||
def clone_kwargs(self):
|
||||
kwargs = super().clone_kwargs()
|
||||
kwargs["children"] = self.children
|
||||
kwargs["permission"] = self.permission
|
||||
return kwargs
|
||||
|
||||
def get_form_options(self):
|
||||
|
@ -543,6 +546,15 @@ class PanelGroup(Panel):
|
|||
return any(child.show_panel_furniture() for child in self.children)
|
||||
|
||||
def is_shown(self):
|
||||
"""
|
||||
Check permissions on the panel group overall then check if any children
|
||||
are shown.
|
||||
"""
|
||||
|
||||
if self.panel.permission:
|
||||
if not self.request.user.has_perm(self.panel.permission):
|
||||
return False
|
||||
|
||||
return any(child.is_shown() for child in self.children)
|
||||
|
||||
@property
|
||||
|
|
|
@ -5,7 +5,7 @@ from unittest import mock
|
|||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.auth.models import AnonymousUser, Permission
|
||||
from django.core import checks
|
||||
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
|
||||
from django.test import RequestFactory, TestCase, override_settings
|
||||
|
@ -374,6 +374,9 @@ class TestTabbedInterface(TestCase, WagtailTestUtils):
|
|||
user = self.create_superuser(username="admin")
|
||||
self.request.user = user
|
||||
self.user = self.login()
|
||||
self.other_user = self.create_user(username="admin2", email="test2@email.com")
|
||||
p = Permission.objects.get(codename="custom_see_panel_setting")
|
||||
self.other_user.user_permissions.add(p)
|
||||
# a custom tabbed interface for EventPage
|
||||
self.event_page_tabbed_interface = TabbedInterface(
|
||||
[
|
||||
|
@ -398,6 +401,20 @@ class TestTabbedInterface(TestCase, WagtailTestUtils):
|
|||
],
|
||||
heading="Secret",
|
||||
),
|
||||
ObjectList(
|
||||
[
|
||||
FieldPanel("cost"),
|
||||
],
|
||||
permission="tests.custom_see_panel_setting",
|
||||
heading="Custom Setting",
|
||||
),
|
||||
ObjectList(
|
||||
[
|
||||
FieldPanel("cost"),
|
||||
],
|
||||
permission="tests.other_custom_see_panel_setting",
|
||||
heading="Other Custom Setting",
|
||||
),
|
||||
]
|
||||
).bind_to_model(EventPage)
|
||||
|
||||
|
@ -477,7 +494,8 @@ class TestTabbedInterface(TestCase, WagtailTestUtils):
|
|||
event = EventPage(title="Abergavenny sheepdog trials")
|
||||
form = EventPageForm(instance=event)
|
||||
|
||||
# when signed in as a superuser all three tabs should be visible
|
||||
with self.subTest("Super user test"):
|
||||
# when signed in as a superuser all tabs should be visible
|
||||
tabbed_interface = self.event_page_tabbed_interface.get_bound_panel(
|
||||
instance=event,
|
||||
form=form,
|
||||
|
@ -496,9 +514,54 @@ class TestTabbedInterface(TestCase, WagtailTestUtils):
|
|||
'<a id="tab-label-secret" href="#tab-secret" ',
|
||||
result,
|
||||
)
|
||||
self.assertIn(
|
||||
'<a id="tab-label-custom_setting" href="#tab-custom_setting" ',
|
||||
result,
|
||||
)
|
||||
self.assertIn(
|
||||
'<a id="tab-label-other_custom_setting" href="#tab-other_custom_setting" ',
|
||||
result,
|
||||
)
|
||||
|
||||
with self.subTest("Not superuser permissions"):
|
||||
"""
|
||||
The super user panel should not show, nor should the panel they dont have
|
||||
permission for.
|
||||
"""
|
||||
self.request.user = self.other_user
|
||||
|
||||
tabbed_interface = self.event_page_tabbed_interface.get_bound_panel(
|
||||
instance=event,
|
||||
form=form,
|
||||
request=self.request,
|
||||
)
|
||||
result = tabbed_interface.render_html()
|
||||
self.assertIn(
|
||||
'<a id="tab-label-event_details" href="#tab-event_details" class="w-tabs__tab shiny" role="tab" aria-selected="false" tabindex="-1">',
|
||||
result,
|
||||
)
|
||||
self.assertIn(
|
||||
'<a id="tab-label-speakers" href="#tab-speakers" class="w-tabs__tab " role="tab" aria-selected="false" tabindex="-1">',
|
||||
result,
|
||||
)
|
||||
self.assertNotIn(
|
||||
'<a id="tab-label-secret" href="#tab-secret" ',
|
||||
result,
|
||||
)
|
||||
self.assertIn(
|
||||
'<a id="tab-label-custom_setting" href="#tab-custom_setting" ',
|
||||
result,
|
||||
)
|
||||
self.assertNotIn(
|
||||
'<a id="tab-label-other_custom_setting" href="#tab-other-custom_setting" ',
|
||||
result,
|
||||
)
|
||||
|
||||
with self.subTest("Non superuser"):
|
||||
# Login as non superuser to check that the third tab does not show
|
||||
user = AnonymousUser() # technically, Anonymous users cannot access the admin
|
||||
user = (
|
||||
AnonymousUser()
|
||||
) # technically, Anonymous users cannot access the admin
|
||||
self.request.user = user
|
||||
tabbed_interface = self.event_page_tabbed_interface.get_bound_panel(
|
||||
instance=event,
|
||||
|
@ -518,6 +581,14 @@ class TestTabbedInterface(TestCase, WagtailTestUtils):
|
|||
'<a id="tab-label-secret" href="#tab-secret" ',
|
||||
result,
|
||||
)
|
||||
self.assertNotIn(
|
||||
'<a id="tab-label-custom_setting" href="#tab-custom_setting" ',
|
||||
result,
|
||||
)
|
||||
self.assertNotIn(
|
||||
'<a id="tab-label-other_custom_setting" href="#tab-other-custom_setting" ',
|
||||
result,
|
||||
)
|
||||
|
||||
|
||||
class TestObjectList(TestCase):
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 4.0.4 on 2022-09-09 14:52
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("tests", "0008_modelwithstringtypeprimarykey"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="eventpage",
|
||||
options={
|
||||
"permissions": [
|
||||
("custom_see_panel_setting", "Can see the panel."),
|
||||
("other_custom_see_panel_setting", "Can see the panel."),
|
||||
]
|
||||
},
|
||||
),
|
||||
]
|
|
@ -407,6 +407,12 @@ class EventPage(Page):
|
|||
FieldPanel("feed_image"),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
permissions = [
|
||||
("custom_see_panel_setting", "Can see the panel."),
|
||||
("other_custom_see_panel_setting", "Can see the panel."),
|
||||
]
|
||||
|
||||
|
||||
class HeadCountRelatedModelUsingPK(models.Model):
|
||||
"""Related model that uses a custom primary key (pk) not id"""
|
||||
|
|
Ładowanie…
Reference in New Issue