diff --git a/HISTORY.rst b/HISTORY.rst index 42662a967c..c181726d98 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -100,6 +100,8 @@ * Backport sidebar code. * CSS updates to maximize usable width. * Improve partial navigation error messages and display. +* Add user created and user edited events. +* Add group created and group edited events. 3.1.11 (2019-04-XX) =================== diff --git a/docs/releases/3.2.rst b/docs/releases/3.2.rst index aa592e6cf3..7c8e81f877 100644 --- a/docs/releases/3.2.rst +++ b/docs/releases/3.2.rst @@ -132,6 +132,8 @@ Other changes * Backport sidebar code. * CSS updates to maximize usable width. * Improve partial navigation error messages and display. +* Add user created and user edited events. +* Add group created and group edited events. Removals -------- diff --git a/mayan/apps/user_management/apps.py b/mayan/apps/user_management/apps.py index a41b17ca61..3736107d93 100644 --- a/mayan/apps/user_management/apps.py +++ b/mayan/apps/user_management/apps.py @@ -14,10 +14,18 @@ from mayan.apps.common.menus import ( menu_user ) from mayan.apps.common.widgets import TwoStateWidget +from mayan.apps.events.classes import ModelEventType +from mayan.apps.events.links import ( + link_events_for_object, link_object_event_types_user_subcriptions_list +) from mayan.apps.metadata import MetadataLookup from mayan.apps.navigation import SourceColumn from mayan.apps.rest_api.fields import DynamicSerializerField +from .events import ( + event_group_created, event_group_edited, event_user_created, + event_user_edited +) from .handlers import handler_initialize_new_user_options from .links import ( link_current_user_details, link_current_user_edit, link_group_create, @@ -28,6 +36,10 @@ from .links import ( link_user_set_password, link_user_setup, separator_user_label, text_user_label ) +from .methods import ( + get_method_group_save, get_method_user_save, method_user_get_absolute_url +) + from .permissions import ( permission_group_delete, permission_group_edit, permission_group_view, permission_user_delete, permission_user_edit, @@ -70,6 +82,13 @@ class UserManagementApp(MayanAppConfig): serializer_class='mayan.apps.user_management.serializers.UserSerializer' ) + # Silence UnorderedObjectListWarning + # "Pagination may yield inconsistent result" + # TODO: Remove on Django 2.x + Group._meta.ordering = ('name',) + + Group.add_to_class(name='save', value=get_method_group_save()) + MetadataLookup( description=_('All the groups.'), name='groups', value=get_groups @@ -78,6 +97,15 @@ class UserManagementApp(MayanAppConfig): description=_('All the users.'), name='users', value=get_users ) + + ModelEventType.register( + event_types=(event_group_created, event_group_edited), model=Group + ) + + ModelEventType.register( + event_types=(event_user_created, event_user_edited), model=User + ) + ModelPermission.register( model=Group, permissions=( permission_acl_edit, permission_acl_view, @@ -115,14 +143,28 @@ class UserManagementApp(MayanAppConfig): ).render() ) + # Silence UnorderedObjectListWarning + # "Pagination may yield inconsistent result" + # TODO: Remove on Django 2.x + User._meta.ordering = ('pk',) + + User.add_to_class( + name='get_absolute_url', value=method_user_get_absolute_url + ) + User.add_to_class(name='save', value=get_method_user_save()) + menu_list_facet.bind_links( links=( - link_acl_list, link_group_members + link_acl_list, link_events_for_object, + link_object_event_types_user_subcriptions_list, + link_group_members, ), sources=(Group,) ) menu_list_facet.bind_links( links=( - link_acl_list, link_user_groups, link_user_set_options + link_acl_list, link_events_for_object, + link_object_event_types_user_subcriptions_list, + link_user_groups, link_user_set_options ), sources=(User,) ) menu_multi_item.bind_links( diff --git a/mayan/apps/user_management/events.py b/mayan/apps/user_management/events.py new file mode 100644 index 0000000000..2d18763e36 --- /dev/null +++ b/mayan/apps/user_management/events.py @@ -0,0 +1,23 @@ +from __future__ import absolute_import, unicode_literals + +from django.utils.translation import ugettext_lazy as _ + +from mayan.apps.events import EventTypeNamespace + +namespace = EventTypeNamespace( + label=_('User management'), name='user_management' +) + +event_group_created = namespace.add_event_type( + label=_('Group created'), name='group_created' +) +event_group_edited = namespace.add_event_type( + label=_('Group edited'), name='group_edited' +) + +event_user_created = namespace.add_event_type( + label=_('User created'), name='user_created' +) +event_user_edited = namespace.add_event_type( + label=_('User edited'), name='user_edited' +) diff --git a/mayan/apps/user_management/methods.py b/mayan/apps/user_management/methods.py new file mode 100644 index 0000000000..240457a5a2 --- /dev/null +++ b/mayan/apps/user_management/methods.py @@ -0,0 +1,60 @@ +from __future__ import unicode_literals + +from django.apps import apps +from django.contrib.auth import get_user_model +from django.db import transaction +from django.shortcuts import reverse + +from .events import ( + event_group_created, event_group_edited, event_user_created, + event_user_edited +) + + +def get_method_group_save(): + Group = apps.get_model(app_label='auth', model_name='Group') + group_save_original = Group.save + + def method_group_save(self, *args, **kwargs): + _user = kwargs.pop('_user', None) + + with transaction.atomic(): + is_new = not self.pk + group_save_original(self, *args, **kwargs) + if is_new: + event_group_created.commit( + actor=_user, target=self + ) + else: + event_group_edited.commit( + actor=_user, target=self + ) + + return method_group_save + + +def method_user_get_absolute_url(self): + return reverse( + viewname='user_management:user_details', kwargs={'pk': self.pk} + ) + + +def get_method_user_save(): + user_save_original = get_user_model().save + + def method_user_save(self, *args, **kwargs): + _user = kwargs.pop('_user', None) + + with transaction.atomic(): + is_new = not self.pk + user_save_original(self, *args, **kwargs) + if is_new: + event_user_created.commit( + actor=_user, target=self + ) + else: + event_user_edited.commit( + actor=_user, target=self + ) + + return method_user_save diff --git a/mayan/apps/user_management/tests/mixins.py b/mayan/apps/user_management/tests/mixins.py index b2a6a9dbd5..fb5c91fada 100644 --- a/mayan/apps/user_management/tests/mixins.py +++ b/mayan/apps/user_management/tests/mixins.py @@ -14,11 +14,15 @@ from .literals import ( class GroupAPITestMixin(object): def _request_test_group_create_api_view(self): - return self.post( + result = self.post( viewname='rest_api:group-list', data={ 'name': TEST_GROUP_NAME } ) + if 'id' in result.json(): + self.test_group = Group.objects.get(pk=result.json()['id']) + + return result def _request_test_group_delete_api_view(self): return self.delete( diff --git a/mayan/apps/user_management/tests/test_events.py b/mayan/apps/user_management/tests/test_events.py new file mode 100644 index 0000000000..d2e35cee05 --- /dev/null +++ b/mayan/apps/user_management/tests/test_events.py @@ -0,0 +1,121 @@ +from __future__ import unicode_literals + +from actstream.models import Action + +from mayan.apps.common.tests import GenericViewTestCase +from mayan.apps.rest_api.tests import BaseAPITestCase + +from ..permissions import ( + permission_group_create, permission_group_edit, permission_user_create, + permission_user_edit +) + +from ..events import ( + event_group_created, event_group_edited, event_user_created, + event_user_edited +) + +from .mixins import ( + GroupAPITestMixin, GroupTestMixin, GroupViewTestMixin, UserAPITestMixin, + UserTestMixin, UserViewTestMixin +) + + +class GroupEventsTestCase(GroupTestMixin, GroupViewTestMixin, UserTestMixin, GenericViewTestCase): + def test_group_create_event(self): + Action.objects.all().delete() + + self.grant_permission( + permission=permission_group_create + ) + self._request_test_group_create_view() + + self.assertEqual(Action.objects.last().target, self.test_group) + self.assertEqual(Action.objects.last().verb, event_group_created.id) + + def test_group_edit_event(self): + self._create_test_group() + Action.objects.all().delete() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + self._request_test_group_edit_view() + + self.assertEqual(Action.objects.last().target, self.test_group) + self.assertEqual(Action.objects.last().verb, event_group_edited.id) + + +class GroupEventsAPITestCase(GroupAPITestMixin, GroupTestMixin, GroupViewTestMixin, BaseAPITestCase): + def test_group_create_event_from_api_view(self): + Action.objects.all().delete() + + self.grant_permission( + permission=permission_group_create + ) + self._request_test_group_create_api_view() + + self.assertEqual(Action.objects.last().target, self.test_group) + self.assertEqual(Action.objects.last().verb, event_group_created.id) + + def test_group_edit_event_from_api_view(self): + self._create_test_group() + Action.objects.all().delete() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + self._request_test_group_edit_patch_api_view() + + self.assertEqual(Action.objects.last().target, self.test_group) + self.assertEqual(Action.objects.last().verb, event_group_edited.id) + + +class UserEventsTestCase(UserAPITestMixin, UserTestMixin, UserViewTestMixin, GenericViewTestCase): + def test_user_create_event_from_view(self): + Action.objects.all().delete() + + self.grant_permission( + permission=permission_user_create + ) + self._request_test_user_create_view() + + self.assertEqual(Action.objects.last().target, self.test_user) + self.assertEqual(Action.objects.last().verb, event_user_created.id) + + def test_user_edit_event_from_view(self): + self._create_test_user() + Action.objects.all().delete() + + self.grant_access( + obj=self.test_user, permission=permission_user_edit + ) + self._request_test_user_edit_view() + + self.assertEqual(Action.objects.last().target, self.test_user) + self.assertEqual(Action.objects.last().verb, event_user_edited.id) + + +class UserEventsAPITestCase(UserAPITestMixin, UserTestMixin, UserViewTestMixin, BaseAPITestCase): + def test_user_create_event_from_api_view(self): + Action.objects.all().delete() + + self.grant_permission( + permission=permission_user_create + ) + self._request_test_user_create_api_view() + + self.assertEqual(Action.objects.last().target, self.test_user) + self.assertEqual(Action.objects.last().verb, event_user_created.id) + + def test_user_edit_event_from_api_view(self): + self._create_test_user() + Action.objects.all().delete() + + self.grant_access( + obj=self.test_user, permission=permission_user_edit + ) + self._request_test_user_edit_patch_api_view() + + self.assertEqual(Action.objects.last().target, self.test_user) + self.assertEqual(Action.objects.last().verb, event_user_edited.id)