Allow GroupViewSet to be customised (#6477)

pull/7178/head
Jan Seifert 2020-10-20 16:01:30 +02:00 zatwierdzone przez Matt Westcott
rodzic 3f128b554e
commit 9dda314263
7 zmienionych plików z 186 dodań i 4 usunięć

Wyświetl plik

@ -0,0 +1,111 @@
Custom group edit/create page
=============================
Custom group edit/create page example
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This example shows how to customize group 'edit' and 'create' page in Wagtail
admin.
Let's say you need to connect Active Directory groups with Django groups.
So create a model for Active Directory groups.
.. code-block:: python
from django.contrib.auth.models import Group
from django.db import models
class ADGroup(models.Model):
guid = models.CharField(verbose_name="GUID", max_length=64, db_index=True, unique=True)
name = models.CharField(verbose_name="Group", max_length=255)
domain = models.CharField(verbose_name="Domain", max_length=255, db_index=True)
description = models.TextField(verbose_name="Description", blank=True, null=True)
roles = models.ManyToManyField(Group, verbose_name="Role", related_name="adgroups", blank=True)
class Meta:
verbose_name = "AD group"
verbose_name_plural = "AD groups"
However, there is no role field on the Wagtail group 'edit' or 'create' page.
To add it, inherit from Wagtail group form and add a new field.
.. code-block:: python
from django import forms
from wagtail.users.forms import GroupForm as WagtailGroupForm
from .models import ADGroup
class GroupForm(WagtailGroupForm):
adgroups = forms.ModelMultipleChoiceField(
label="AD groups",
required=False,
queryset=ADGroup.objects.order_by("name"),
)
class Meta(WagtailGroupForm.Meta):
fields = WagtailGroupForm.Meta.fields + ("adgroups",)
def __init__(self, initial=None, instance=None, **kwargs):
if instance is not None:
if initial is None:
initial = {}
initial["adgroups"] = instance.adgroups.all()
super().__init__(initial=initial, instance=instance, **kwargs)
def save(self, commit=True):
instance = super().save()
instance.adgroups.set(self.cleaned_data["adgroups"])
return instance
Now add your custom form into group viewset by inheriting default Wagtail
``GroupViewSet`` class and overriding ``get_form_class`` method.
.. code-block:: python
from wagtail.users.views.groups import GroupViewSet as WagtailGroupViewSet
from .forms import GroupForm
class GroupViewSet(WagtailGroupViewSet):
def get_form_class(self, for_update=False):
return GroupForm
Append the field into group 'edit'/'create' templates.
.. code-block:: html+Django
{% extends "wagtailusers/groups/edit.html" %}
{% load wagtailusers_tags wagtailadmin_tags i18n %}
{% block extra_fields %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.adgroups %}
{% endblock extra_fields %}
Finally configure ``wagtail.users`` application for using the viewset. Create
``myapplication/apps.py`` module in the main application package and configure
``AppConfig``.
.. code-block:: python
from wagtail.users.apps import WagtailUsersAppConfig
class CustomUsersAppConfig(WagtailUsersAppConfig):
group_viewset = "myapplication.someapp.viewsets.GroupViewSet"
And put path to ``CustomUsersAppConfig`` into ``settings.INSTALLED_APPS``
instead of ``wagtail.users``.
.. code-block:: python
INSTALLED_APPS = [
...,
"myapplication.apps.CustomUsersAppConfig",
# "wagtail.users",
...,
]

Wyświetl plik

@ -11,5 +11,6 @@ Customising Wagtail
extending_hallo extending_hallo
admin_templates admin_templates
custom_user_models custom_user_models
custom_group_viewset
streamfield_blocks streamfield_blocks
custom_account_settings custom_account_settings

Wyświetl plik

@ -7,3 +7,4 @@ class WagtailUsersAppConfig(AppConfig):
label = 'wagtailusers' label = 'wagtailusers'
verbose_name = _("Wagtail users") verbose_name = _("Wagtail users")
default_auto_field = 'django.db.models.AutoField' default_auto_field = 'django.db.models.AutoField'
group_viewset = None

Wyświetl plik

@ -20,7 +20,10 @@
{% csrf_token %} {% csrf_token %}
<ul class="fields"> <ul class="fields">
{% include "wagtailadmin/shared/field_as_li.html" with field=form.name %} {% block fields %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.name %}
{% block extra_fields %}{% endblock extra_fields %}
{% endblock fields %}
<li> <li>
{% format_permissions permission_bound_field=form.permissions %} {% format_permissions permission_bound_field=form.permissions %}
</li> </li>

Wyświetl plik

@ -22,7 +22,10 @@
{% csrf_token %} {% csrf_token %}
<ul class="fields"> <ul class="fields">
{% include "wagtailadmin/shared/field_as_li.html" with field=form.name %} {% block fields %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.name %}
{% block extra_fields %}{% endblock extra_fields %}
{% endblock fields %}
<li> <li>
{% format_permissions permission_bound_field=form.permissions %} {% format_permissions permission_bound_field=form.permissions %}

Wyświetl plik

@ -1,6 +1,7 @@
import unittest import unittest.mock
from django import forms from django import forms
from django.apps import apps
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission from django.contrib.auth.models import Group, Permission
@ -16,7 +17,9 @@ from wagtail.core.models import Collection, GroupCollectionPermission, GroupPage
from wagtail.tests.utils import WagtailTestUtils from wagtail.tests.utils import WagtailTestUtils
from wagtail.users.forms import UserCreationForm, UserEditForm from wagtail.users.forms import UserCreationForm, UserEditForm
from wagtail.users.models import UserProfile from wagtail.users.models import UserProfile
from wagtail.users.views.groups import GroupViewSet
from wagtail.users.views.users import get_user_creation_form, get_user_edit_form from wagtail.users.views.users import get_user_creation_form, get_user_edit_form
from wagtail.users.wagtail_hooks import get_group_viewset_cls
delete_user_perm_codename = "delete_{0}".format(AUTH_USER_MODEL_NAME.lower()) delete_user_perm_codename = "delete_{0}".format(AUTH_USER_MODEL_NAME.lower())
@ -37,6 +40,10 @@ class CustomUserEditForm(UserEditForm):
attachment = forms.FileField(required=True, label="Attachment") attachment = forms.FileField(required=True, label="Attachment")
class CustomGroupViewSet(GroupViewSet):
icon = 'custom-icon'
class TestUserFormHelpers(TestCase): class TestUserFormHelpers(TestCase):
def test_get_user_edit_form_with_default_form(self): def test_get_user_edit_form_with_default_form(self):
@ -1576,3 +1583,33 @@ class TestGroupEditView(TestCase, WagtailTestUtils):
# See that the non-registered permission is still there # See that the non-registered permission is still there
self.assertEqual(self.test_group.permissions.count(), 1) self.assertEqual(self.test_group.permissions.count(), 1)
self.assertEqual(self.test_group.permissions.all()[0], self.non_registered_perm) self.assertEqual(self.test_group.permissions.all()[0], self.non_registered_perm)
class TestGroupViewSet(TestCase):
def setUp(self):
self.app_config = apps.get_app_config('wagtailusers')
def test_get_group_viewset_cls(self):
self.assertIs(get_group_viewset_cls(self.app_config), GroupViewSet)
def test_get_group_viewset_cls_with_custom_form(self):
with unittest.mock.patch.object(
self.app_config, 'group_viewset', new='wagtail.users.tests.CustomGroupViewSet'
):
group_viewset = get_group_viewset_cls(self.app_config)
self.assertIs(group_viewset, CustomGroupViewSet)
self.assertEqual(group_viewset.icon, 'custom-icon')
def test_get_group_viewset_cls_custom_form_invalid_value(self):
with unittest.mock.patch.object(self.app_config, 'group_viewset', new=12345):
with self.assertRaises(ImproperlyConfigured) as exc_info:
get_group_viewset_cls(self.app_config)
self.assertTrue('refers to a class that is not class path' in str(exc_info.exception))
def test_get_group_viewset_cls_custom_form_does_not_exist(self):
with unittest.mock.patch.object(
self.app_config, 'group_viewset', new='wagtail.users.tests.CustomClassDoesNotExist'
):
with self.assertRaises(ImproperlyConfigured) as exc_info:
get_group_viewset_cls(self.app_config)
self.assertTrue('refers to a class that is not available' in str(exc_info.exception))

Wyświetl plik

@ -1,6 +1,9 @@
from django.apps import apps
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q from django.db.models import Q
from django.urls import include, path, reverse from django.urls import include, path, reverse
from django.utils.module_loading import import_string
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from wagtail.admin.menu import MenuItem from wagtail.admin.menu import MenuItem
@ -20,9 +23,32 @@ def register_admin_urls():
] ]
def get_group_viewset_cls(app_config):
if app_config.group_viewset is None:
group_viewset_cls = GroupViewSet
else:
if not isinstance(app_config.group_viewset, str):
raise ImproperlyConfigured(
"'{:s}.group_viewset' refers to a class that is not class path".format(
app_config.__class__.__name__
)
)
try:
group_viewset_cls = import_string(app_config.group_viewset)
except ImportError:
raise ImproperlyConfigured(
"'{:s}.group_viewset' refers to a class that is not available".format(
app_config.__class__.__name__
)
)
return group_viewset_cls
@hooks.register('register_admin_viewset') @hooks.register('register_admin_viewset')
def register_viewset(): def register_viewset():
return GroupViewSet('wagtailusers_groups', url_prefix='groups') app_config = apps.get_app_config("wagtailusers")
group_viewset_cls = get_group_viewset_cls(app_config)
return group_viewset_cls('wagtailusers_groups', url_prefix='groups')
# Typically we would check the permission 'auth.change_user' (and 'auth.add_user' / # Typically we would check the permission 'auth.change_user' (and 'auth.add_user' /