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 + )