From 18e5ee1e4fb19b747d74e4b5f80119c815290a5d Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Thu, 14 Feb 2019 02:30:51 -0400 Subject: [PATCH] ACL app updates Update the ACL permission view to use the new AddRemoveView. Add ACL created and ACL edit events. Add permission adding and removal accesors to the ACL model. Signed-off-by: Roberto Rosario --- mayan/apps/acls/apps.py | 28 +++++-- mayan/apps/acls/events.py | 16 ++++ mayan/apps/acls/managers.py | 8 +- mayan/apps/acls/models.py | 37 ++++++++- mayan/apps/acls/views.py | 145 +++++++++++++++--------------------- 5 files changed, 132 insertions(+), 102 deletions(-) create mode 100644 mayan/apps/acls/events.py diff --git a/mayan/apps/acls/apps.py b/mayan/apps/acls/apps.py index 5d763cd628..b15022bce8 100644 --- a/mayan/apps/acls/apps.py +++ b/mayan/apps/acls/apps.py @@ -2,12 +2,16 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ -from mayan.apps.common import ( - MayanAppConfig, menu_object, menu_secondary, menu_sidebar +from mayan.apps.common import MayanAppConfig, menu_object, menu_secondary +from mayan.apps.events import ModelEventType +from mayan.apps.events.links import ( + link_events_for_object, link_object_event_types_user_subcriptions_list ) +from mayan.apps.events.permissions import permission_events_view from mayan.apps.navigation import SourceColumn from .classes import ModelPermission +from .events import event_acl_created, event_acl_edited from .links import link_acl_create, link_acl_delete, link_acl_permissions @@ -21,25 +25,33 @@ class ACLsApp(MayanAppConfig): def ready(self): super(ACLsApp, self).ready() + from actstream import registry AccessControlList = self.get_model(model_name='AccessControlList') + ModelEventType.register( + event_types=(event_acl_created, event_acl_edited), + model=AccessControlList + ) ModelPermission.register_inheritance( model=AccessControlList, related='content_object', ) + SourceColumn( attribute='role', is_identifier=True, is_sortable=True, source=AccessControlList ) - SourceColumn( - attribute='get_permission_titles', include_label=True, - source=AccessControlList - ) menu_object.bind_links( - links=(link_acl_permissions, link_acl_delete,), + links=( + link_acl_permissions, link_acl_delete, + link_events_for_object, + link_object_event_types_user_subcriptions_list + ), sources=(AccessControlList,) ) - menu_sidebar.bind_links( + menu_secondary.bind_links( links=(link_acl_create,), sources=('acls:acl_list',) ) + + registry.register(AccessControlList) diff --git a/mayan/apps/acls/events.py b/mayan/apps/acls/events.py new file mode 100644 index 0000000000..8f75bc26e1 --- /dev/null +++ b/mayan/apps/acls/events.py @@ -0,0 +1,16 @@ +from __future__ import absolute_import, unicode_literals + +from django.utils.translation import ugettext_lazy as _ + +from mayan.apps.events import EventTypeNamespace + +namespace = EventTypeNamespace( + label=_('Access control lists'), name='acls' +) + +event_acl_created = namespace.add_event_type( + label=_('ACL created'), name='acl_created' +) +event_acl_edited = namespace.add_event_type( + label=_('ACL edited'), name='acl_edited' +) diff --git a/mayan/apps/acls/managers.py b/mayan/apps/acls/managers.py index 8b55a5af1c..d113be9254 100644 --- a/mayan/apps/acls/managers.py +++ b/mayan/apps/acls/managers.py @@ -151,7 +151,7 @@ class AccessControlListManager(models.Manager): else: raise PermissionDenied - def get_inherited_permissions(self, role, obj): + def get_inherited_permissions(self, obj, role): try: instance = obj.first() except AttributeError: @@ -177,11 +177,11 @@ class AccessControlListManager(models.Manager): parent_object = return_related( instance=instance, related_field=parent_accessor ) - content_type = ContentType.objects.get_for_model(parent_object) + content_type = ContentType.objects.get_for_model(model=parent_object) try: return self.get( - role=role, content_type=content_type, - object_id=parent_object.pk + content_type=content_type, object_id=parent_object.pk, + role=role ).permissions.all() except self.model.DoesNotExist: return StoredPermission.objects.none() diff --git a/mayan/apps/acls/models.py b/mayan/apps/acls/models.py index 4a7fb10726..997919364e 100644 --- a/mayan/apps/acls/models.py +++ b/mayan/apps/acls/models.py @@ -4,13 +4,14 @@ import logging from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType -from django.db import models +from django.db import models, transaction from django.urls import reverse from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from mayan.apps.permissions.models import Role, StoredPermission +from .events import event_acl_created, event_acl_edited from .managers import AccessControlListManager logger = logging.getLogger(__name__) @@ -58,11 +59,10 @@ class AccessControlList(models.Model): def __str__(self): return _( - 'Permissions "%(permissions)s" to role "%(role)s" for "%(object)s"' + 'Role "%(role)s" permission\'s for "%(object)s"' ) % { - 'permissions': self.get_permission_titles(), 'object': self.content_object, - 'role': self.role + 'role': self.role, } def get_absolute_url(self): @@ -85,3 +85,32 @@ class AccessControlList(models.Model): return result or _('None') get_permission_titles.short_description = _('Permissions') + + def permissions_add(self, queryset, _user=None): + with transaction.atomic(): + event_acl_edited.commit( + actor=_user, target=self + ) + self.permissions.add(*queryset) + + def permissions_remove(self, queryset, _user=None): + with transaction.atomic(): + event_acl_edited.commit( + actor=_user, target=self + ) + self.permissions.remove(*queryset) + + def save(self, *args, **kwargs): + _user = kwargs.pop('_user', None) + + with transaction.atomic(): + is_new = not self.pk + super(AccessControlList, self).save(*args, **kwargs) + if is_new: + event_acl_created.commit( + actor=_user, target=self + ) + else: + event_acl_edited.commit( + actor=_user, target=self + ) diff --git a/mayan/apps/acls/views.py b/mayan/apps/acls/views.py index f4d43ffdd1..9d51e81140 100644 --- a/mayan/apps/acls/views.py +++ b/mayan/apps/acls/views.py @@ -1,9 +1,7 @@ from __future__ import absolute_import, unicode_literals -import itertools import logging -from django.shortcuts import get_object_or_404 from django.template import RequestContext from django.urls import reverse from django.utils.encoding import force_text @@ -13,11 +11,10 @@ from mayan.apps.common.mixins import ( ContentTypeViewMixin, ExternalObjectMixin ) from mayan.apps.common.generics import ( - AssignRemoveView, SingleObjectCreateView, SingleObjectDeleteView, + AddRemoveView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectListView ) -from mayan.apps.permissions import Permission, PermissionNamespace -from mayan.apps.permissions.models import Role, StoredPermission +from mayan.apps.permissions.models import Role from .classes import ModelPermission from .forms import ACLCreateForm @@ -60,7 +57,8 @@ class ACLCreateView(ContentTypeViewMixin, ExternalObjectMixin, SingleObjectCreat 'queryset': Role.objects.exclude( pk__in=self.get_external_object().acls.values('role') ), - 'widget_attributes': {'class': 'select2'} + 'widget_attributes': {'class': 'select2'}, + '_user': self.request.user } def get_instance_extra_data(self): @@ -140,109 +138,84 @@ class ACLListView(ContentTypeViewMixin, ExternalObjectMixin, SingleObjectListVie return self.get_external_object().acls.all() -class ACLPermissionsView(AssignRemoveView): - grouped = True - left_list_title = _('Available permissions') - right_list_title = _('Granted permissions') +class ACLPermissionsView(AddRemoveView): + action_add_method = 'permissions_add' + action_remove_method = 'permissions_remove' + main_object_model = AccessControlList + main_object_permission = permission_acl_edit + main_object_pk_url_kwarg = 'acl_id' + list_added_title = _('Granted permissions') + list_available_title = _('Available permissions') + related_field = 'permissions' - @staticmethod - def generate_choices(entries): - results = [] + def generate_choices(self, queryset): + namespaces_dictionary = {} - entries = sorted( - entries, key=lambda x: ( - x.volatile_permission.namespace.label, - x.volatile_permission.label - ) + # Sort permissions by their translatable label + object_list = sorted( + queryset, key=lambda permission: permission.volatile_permission.label ) - for namespace, permissions in itertools.groupby(entries, lambda entry: entry.namespace): - permission_options = [ - (force_text(permission.pk), permission) for permission in permissions - ] - results.append( - (PermissionNamespace.get(name=namespace), permission_options) + # Group permissions by namespace + for permission in object_list: + namespaces_dictionary.setdefault( + permission.volatile_permission.namespace.label, + [] + ) + namespaces_dictionary[permission.volatile_permission.namespace.label].append( + (permission.pk, force_text(permission)) ) - return results + # Sort permissions by their translatable namespace label + return sorted(namespaces_dictionary.items()) - def add(self, item): - permission = get_object_or_404(klass=StoredPermission, pk=item) - self.get_object().permissions.add(permission) - - def get_available_list(self): - return ModelPermission.get_for_instance( - instance=self.get_object().content_object - ).exclude(id__in=self.get_granted_list().values_list('pk', flat=True)) + def get_actions_extra_kwargs(self): + return {'_user': self.request.user} def get_disabled_choices(self): """ - Get permissions from a parent's acls but remove the permissions we - already hold for this object + Get permissions from a parent's ACLs. We return a list since that is + what the form widget's can process. """ - return map( - str, set( - self.get_object().get_inherited_permissions().values_list( - 'pk', flat=True - ) - ).difference( - self.get_object().permissions.values_list('pk', flat=True) - ) + return self.main_object.get_inherited_permissions().values_list( + 'pk', flat=True ) def get_extra_context(self): - acl = self.get_object() - return { - 'acl': acl, - 'object': acl.content_object, + 'acl': self.main_object, + 'object': self.main_object.content_object, 'navigation_object_list': ('object', 'acl'), - 'title': _('Role "%(role)s" permission\'s for "%(object)s"') % { - 'role': acl.role, - 'object': acl.content_object, + 'title': _('Role "%(role)s" permission\'s for "%(object)s".') % { + 'role': self.main_object.role, + 'object': self.main_object.content_object, } } - def get_granted_list(self): - """ - Merge of permissions we hold for this object and the permissions we - hold for this object's parent via another ACL. - """ - merged_pks = self.get_object().permissions.values_list( - 'pk', flat=True - ) | self.get_object().get_inherited_permissions().values_list( - 'pk', flat=True - ) - return StoredPermission.objects.filter(pk__in=merged_pks) - - def get_object(self): - return get_object_or_404( - klass=self.get_queryset(), pk=self.kwargs['acl_id'] - ) - - def get_queryset(self): - return AccessControlList.objects.restrict_queryset( - permission=permission_acl_edit, - queryset=AccessControlList.objects.all(), user=self.request.user - ) - - def get_right_list_help_text(self): - if self.get_object().get_inherited_permissions(): + def get_list_added_help_text(self): + if self.main_object.get_inherited_permissions(): return _( 'Disabled permissions are inherited from a parent object and ' 'can\'t be removed from this view, they need to be removed ' 'from the parent object\'s ACL view.' ) - return self.right_list_help_text + def get_list_added_queryset(self): + """ + Merge of permissions we hold for this object and the permissions we + hold for this object's parents via another ACL. .distinct() is added + in case the permission was added to the ACL and then added to a + parent ACL's and thus inherited and would appear twice. If + order to remove the double permission from the ACL it would need to be + remove from the parent first to enable the choice in the form, + remove it from the ACL and then re-add it to the parent ACL. + """ + queryset = super(ACLPermissionsView, self).get_list_added_queryset() + return ( + queryset | self.main_object.get_inherited_permissions() + ).distinct() - def left_list(self): - Permission.refresh() - return ACLPermissionsView.generate_choices(self.get_available_list()) - - def remove(self, item): - permission = get_object_or_404(klass=StoredPermission, pk=item) - self.get_object().permissions.remove(permission) - - def right_list(self): - return ACLPermissionsView.generate_choices(self.get_granted_list()) + def get_secondary_object_source_queryset(self): + return ModelPermission.get_for_instance( + instance=self.main_object.content_object + )