Add permission inheritance by parent object. Add ACLs app model tests.

This commit is contained in:
Roberto Rosario
2015-07-10 01:40:21 -04:00
parent 441eae28bc
commit bc3eed143c
9 changed files with 337 additions and 90 deletions

View File

@@ -3,7 +3,7 @@ python:
- 2.7 - 2.7
env: env:
global: global:
- TEST_APPS="authentication django_gpg document_indexing document_signatures documents dynamic_search folders lock_manager ocr permissions sources tags" - TEST_APPS="acls authentication django_gpg document_indexing document_signatures documents dynamic_search folders lock_manager ocr permissions sources tags"
matrix: matrix:
- DB=mysql - DB=mysql
- DB=postgres - DB=postgres

View File

@@ -9,6 +9,8 @@ logger = logging.getLogger(__name__)
class ModelPermission(object): class ModelPermission(object):
_registry = {} _registry = {}
_proxies = {}
_inheritances = {}
@classmethod @classmethod
def register(cls, model, permissions): def register(cls, model, permissions):
@@ -18,6 +20,25 @@ class ModelPermission(object):
@classmethod @classmethod
def get_for_instance(cls, instance): def get_for_instance(cls, instance):
permissions = cls._registry.get(type(instance), ()) try:
permissions = cls._registry[type(instance)]
except KeyError:
try:
permissions = cls._registry[cls._proxies[type(instance)]]
except KeyError:
permissions = ()
pks = [permission.stored_permission.pk for permission in permissions] pks = [permission.stored_permission.pk for permission in permissions]
return StoredPermission.objects.filter(pk__in=pks) return StoredPermission.objects.filter(pk__in=pks)
@classmethod
def register_proxy(cls, source, model):
cls._proxies[model] = source
@classmethod
def register_inheritance(cls, model, related):
cls._inheritances[model] = related
@classmethod
def get_inheritance(cls, model):
return cls._inheritances[model]

View File

@@ -1,12 +1,17 @@
from __future__ import unicode_literals from __future__ import absolute_import, unicode_literals
import logging import logging
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import models from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext from django.utils.translation import ugettext
from permissions.models import StoredPermission
from .classes import ModelPermission
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -16,20 +21,44 @@ class AccessControlListManager(models.Manager):
and an object and an object
""" """
def get_inherited_permissions(self, role, obj):
try:
instance = obj.first()
except AttributeError:
instance = obj
else:
if not instance:
return StoredPermission.objects.none()
try:
parent_accessor = ModelPermission.get_inheritance(type(instance))
except KeyError:
return StoredPermission.objects.none()
else:
parent_object = getattr(instance, parent_accessor)
content_type = ContentType.objects.get_for_model(parent_object)
try:
return self.get(role=role, content_type=content_type, object_id=parent_object.pk).permissions.all()
except self.model.DoesNotExist:
return StoredPermission.objects.none()
def check_access(self, permissions, user, obj): def check_access(self, permissions, user, obj):
if user.is_superuser or user.is_staff: if user.is_superuser or user.is_staff:
return True return True
user_roles = []
for group in user.groups.all():
for role in group.roles.all():
user_roles.append(role)
try: try:
stored_permissions = [permission.stored_permission for permission in permissions] stored_permissions = [permission.stored_permission for permission in permissions]
except TypeError: except TypeError:
stored_permissions = [permissions.stored_permission] stored_permissions = [permissions.stored_permission]
user_roles = []
for group in user.groups.all():
for role in group.roles.all():
if set(stored_permissions).intersection(set(self.get_inherited_permissions(role=role, obj=obj))):
return True
user_roles.append(role)
if not self.filter(content_type=ContentType.objects.get_for_model(obj), object_id=obj.pk, permissions__in=stored_permissions, role__in=user_roles): if not self.filter(content_type=ContentType.objects.get_for_model(obj), object_id=obj.pk, permissions__in=stored_permissions, role__in=user_roles):
raise PermissionDenied(ugettext('Insufficient access.')) raise PermissionDenied(ugettext('Insufficient access.'))
@@ -42,11 +71,21 @@ class AccessControlListManager(models.Manager):
for role in group.roles.all(): for role in group.roles.all():
user_roles.append(role) user_roles.append(role)
parent_accessor = ModelPermission.get_inheritance(queryset.model)
instance = queryset.first()
if instance:
parent_object = getattr(instance, parent_accessor)
parent_content_type = ContentType.objects.get_for_model(parent_object)
parent_queryset = self.filter(content_type=parent_content_type, role__in=user_roles, permissions=permission.stored_permission)
parent_acl_query = Q(**{'{}__pk__in'.format(parent_accessor): parent_queryset.values_list('pk', flat=True)})
else:
parent_acl_query = Q()
# Directly granted access
content_type = ContentType.objects.get_for_model(queryset.model) content_type = ContentType.objects.get_for_model(queryset.model)
acl_query = Q(pk__in=self.filter(content_type=content_type, role__in=user_roles, permissions=permission.stored_permission).values_list('object_id', flat=True))
acls = self.filter(content_type=content_type, role__in=user_roles, permissions=permission.stored_permission).values_list('object_id', flat=True) new_queryset = queryset.filter(parent_acl_query | acl_query)
new_queryset = queryset.filter(pk__in=acls)
if new_queryset.count() == 0 and exception_on_empty: if new_queryset.count() == 0 and exception_on_empty:
raise PermissionDenied raise PermissionDenied

View File

@@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from permissions.models import Role, StoredPermission from permissions.models import Role, StoredPermission
from .classes import ModelPermission
from .managers import AccessControlListManager from .managers import AccessControlListManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -43,3 +44,6 @@ class AccessControlList(models.Model):
def __str__(self): def __str__(self):
return '{} <=> {}'.format(self.content_object, self.role) return '{} <=> {}'.format(self.content_object, self.role)
def get_inherited_permissions(self):
return AccessControlList.objects.get_inherited_permissions(role=self.role, obj=self.content_object)

View File

@@ -0,0 +1,146 @@
from __future__ import absolute_import, unicode_literals
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.exceptions import PermissionDenied
from django.core.files import File
from django.core.urlresolvers import reverse
from django.test.client import Client
from django.test import TestCase
from documents.models import Document, DocumentType
from documents.permissions import permission_document_view
from documents.test_models import (
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL,
TEST_SMALL_DOCUMENT_FILENAME, TEST_NON_ASCII_DOCUMENT_FILENAME,
TEST_NON_ASCII_COMPRESSED_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH,
TEST_SIGNED_DOCUMENT_PATH, TEST_SMALL_DOCUMENT_PATH,
TEST_NON_ASCII_DOCUMENT_PATH, TEST_NON_ASCII_COMPRESSED_DOCUMENT_PATH,
TEST_DOCUMENT_DESCRIPTION, TEST_DOCUMENT_TYPE
)
from permissions.classes import Permission
from permissions.models import Role
from .models import AccessControlList
class PermissionTestCase(TestCase):
def setUp(self):
self.document_type_1 = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE)
ocr_settings = self.document_type_1.ocr_settings
ocr_settings.auto_ocr = False
ocr_settings.save()
self.document_type_2 = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE + '2')
ocr_settings = self.document_type_2.ocr_settings
ocr_settings.auto_ocr = False
ocr_settings.save()
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
self.document_1 = self.document_type_1.new_document(file_object=File(file_object), label='document 1')
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
self.document_2 = self.document_type_1.new_document(file_object=File(file_object), label='document 2')
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
self.document_3 = self.document_type_2.new_document(file_object=File(file_object), label='document 3')
self.user = get_user_model().objects.create(username='test user')
self.group = Group.objects.create(name='test group')
self.role = Role.objects.create(label='test role')
Permission.invalidate_cache()
def test_check_access_without_permissions(self):
with self.assertRaises(PermissionDenied):
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_1)
def test_filtering_without_permissions(self):
self.assertEqual(
list(AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())),
[]
)
def test_check_access_with_acl(self):
self.group.user_set.add(self.user)
self.role.groups.add(self.group)
acl = AccessControlList.objects.create(content_object=self.document_1, role=self.role)
acl.permissions.add(permission_document_view.stored_permission)
try:
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_1)
except PermissionDenied:
self.fail('PermissionDenied exception was not expected.')
def test_filtering_with_permissions(self):
self.group.user_set.add(self.user)
self.role.permissions.add(permission_document_view.stored_permission)
self.role.groups.add(self.group)
acl = AccessControlList.objects.create(content_object=self.document_1, role=self.role)
acl.permissions.add(permission_document_view.stored_permission)
self.assertEqual(
list(AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())),
[self.document_1]
)
def test_check_access_with_inherited_acl(self):
self.group.user_set.add(self.user)
self.role.groups.add(self.group)
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
acl.permissions.add(permission_document_view.stored_permission)
try:
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_1)
except PermissionDenied:
self.fail('PermissionDenied exception was not expected.')
def test_check_access_with_inherited_acl_and_local_acl(self):
self.group.user_set.add(self.user)
self.role.groups.add(self.group)
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
acl.permissions.add(permission_document_view.stored_permission)
acl = AccessControlList.objects.create(content_object=self.document_3, role=self.role)
acl.permissions.add(permission_document_view.stored_permission)
try:
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_3)
except PermissionDenied:
self.fail('PermissionDenied exception was not expected.')
def test_filtering_with_inherited_permissions(self):
self.group.user_set.add(self.user)
self.role.permissions.add(permission_document_view.stored_permission)
self.role.groups.add(self.group)
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
acl.permissions.add(permission_document_view.stored_permission)
result = AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())
self.assertTrue(self.document_1 in result)
self.assertTrue(self.document_2 in result)
self.assertTrue(self.document_3 not in result)
def test_filtering_with_inherited_permissions_and_local_acl(self):
self.group.user_set.add(self.user)
self.role.permissions.add(permission_document_view.stored_permission)
self.role.groups.add(self.group)
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
acl.permissions.add(permission_document_view.stored_permission)
acl = AccessControlList.objects.create(content_object=self.document_3, role=self.role)
acl.permissions.add(permission_document_view.stored_permission)
result = AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())
self.assertTrue(self.document_1 in result)
self.assertTrue(self.document_2 in result)
self.assertTrue(self.document_3 in result)

View File

@@ -5,6 +5,7 @@ import logging
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@@ -24,11 +25,11 @@ from .permissions import permission_acl_edit, permission_acl_view
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _permission_titles(permission_list):
return ', '.join([unicode(permission) for permission in permission_list])
class ACLListView(SingleObjectListView): class ACLListView(SingleObjectListView):
@staticmethod
def permission_titles(permission_list):
return ', '.join([unicode(permission) for permission in permission_list])
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model']) self.content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model'])
@@ -47,32 +48,27 @@ class ACLListView(SingleObjectListView):
def get_queryset(self): def get_queryset(self):
return AccessControlList.objects.filter(content_type=self.content_type, object_id=self.content_object.pk) return AccessControlList.objects.filter(content_type=self.content_type, object_id=self.content_object.pk)
def get_context_data(self, **kwargs): def get_extra_context(self):
context = super(ACLListView, self).get_context_data(**kwargs) return {
context.update( 'hide_object': True,
{ 'object': self.content_object,
'hide_object': True, 'title': _('Access control lists for: %s' % self.content_object),
'object': self.content_object, 'extra_columns': [
'title': _('Access control lists for: %s' % self.content_object), {
'extra_columns': [ 'name': _('Role'),
{ 'attribute': 'role'
'name': _('Role'), },
'attribute': 'role' {
}, 'name': _('Permissions'),
{ 'attribute': encapsulate(lambda entry: ACLListView.permission_titles(entry.permissions.all()))
'name': _('Permissions'), },
'attribute': encapsulate(lambda x: _permission_titles(x.permissions.all())) ],
}, }
],
}
)
return context
class ACLCreateView(SingleObjectCreateView): class ACLCreateView(SingleObjectCreateView):
model = AccessControlList
fields = ('role',) fields = ('role',)
model = AccessControlList
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model']) content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model'])
@@ -90,12 +86,15 @@ class ACLCreateView(SingleObjectCreateView):
return super(ACLCreateView, self).dispatch(request, *args, **kwargs) return super(ACLCreateView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form): def form_valid(self, form):
instance = form.save(commit=False) self.instance = form.save(commit=False)
instance.content_object = self.content_object self.instance.content_object = self.content_object
instance.save() self.instance.save()
return super(ACLCreateView, self).form_valid(form) return super(ACLCreateView, self).form_valid(form)
def get_success_url(self):
return reverse('acls:acl_permissions', args=[self.instance.pk])
def get_extra_context(self): def get_extra_context(self):
return { return {
'object': self.content_object, 'object': self.content_object,
@@ -105,7 +104,16 @@ class ACLCreateView(SingleObjectCreateView):
class ACLDeleteView(SingleObjectDeleteView): class ACLDeleteView(SingleObjectDeleteView):
model = AccessControlList model = AccessControlList
object_permission = permission_acl_edit
def dispatch(self, request, *args, **kwargs):
acl = get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
try:
Permission.check_permissions(request.user, permissions=(permission_acl_edit,))
except PermissionDenied:
AccessControlList.objects.check_access(permission_acl_edit, request.user, acl.content_object)
return super(ACLDeleteView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(ACLDeleteView, self).get_context_data(**kwargs) context = super(ACLDeleteView, self).get_context_data(**kwargs)
@@ -120,45 +128,74 @@ class ACLDeleteView(SingleObjectDeleteView):
class ACLPermissionsView(AssignRemoveView): class ACLPermissionsView(AssignRemoveView):
grouped = True grouped = True
object_permission = permission_acl_edit
left_list_title = _('Available permissions') left_list_title = _('Available permissions')
right_list_title = _('Granted permissions') right_list_title = _('Granted permissions')
@staticmethod
def generate_choices(entries):
results = []
for namespace, permissions in itertools.groupby(entries, lambda entry: entry.namespace):
permission_options = [(unicode(permission.pk), permission) for permission in permissions]
results.append((PermissionNamespace.get(namespace), permission_options))
return results
def add(self, item): def add(self, item):
permission = get_object_or_404(StoredPermission, pk=item) permission = get_object_or_404(StoredPermission, pk=item)
self.get_object().permissions.add(permission) self.get_object().permissions.add(permission)
def dispatch(self, request, *args, **kwargs):
acl = get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
try:
Permission.check_permissions(request.user, permissions=(permission_acl_edit,))
except PermissionDenied:
AccessControlList.objects.check_access(permission_acl_edit, request.user, acl.content_object)
return super(ACLPermissionsView, self).dispatch(request, *args, **kwargs)
def get_help_text(self):
if self.get_object().get_inherited_permissions():
return _('Disabled permissions are inherited from a parent object.')
return None
def get_object(self): def get_object(self):
return get_object_or_404(AccessControlList, pk=self.kwargs['pk']) return get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
def left_list(self): def get_available_list(self):
results = [] return ModelPermission.get_for_instance(instance=self.get_object().content_object).exclude(id__in=self.get_granted_list().values_list('pk', flat=True))
for namespace, permissions in itertools.groupby(ModelPermission.get_for_instance(instance=self.get_object().content_object).exclude(id__in=self.get_object().permissions.values_list('pk', flat=True)), lambda entry: entry.namespace):
permission_options = [(unicode(permission.pk), permission) for permission in permissions]
results.append((PermissionNamespace.get(namespace), permission_options))
return results def get_disabled_choices(self):
"""
Get permissions from a parent's acls but remove the permissions we
already hold for this object
"""
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)))
def get_extra_context(self):
return {
'object': self.get_object().content_object,
'title': _('Role "%(role)s" permission\'s for "%(object)s"') % {
'role': self.get_object().role,
'object': self.get_object().content_object,
},
}
def get_granted_list(self):
"""
Merge or 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 left_list(self):
return ACLPermissionsView.generate_choices(self.get_available_list())
def right_list(self): def right_list(self):
results = [] return ACLPermissionsView.generate_choices(self.get_granted_list())
for namespace, permissions in itertools.groupby(self.get_object().permissions.all(), lambda entry: entry.namespace):
permission_options = [(unicode(permission.pk), permission) for permission in permissions]
results.append((PermissionNamespace.get(namespace), permission_options))
return results
def get_context_data(self, **kwargs):
context = super(ACLPermissionsView, self).get_context_data(**kwargs)
context.update(
{
'object': self.get_object().content_object,
'title': _('Role "%(role)s" permission\'s for "%(object)s"') % {
'role': self.get_object().role,
'object': self.get_object().content_object,
}
}
)
return context
def remove(self, item): def remove(self, item):
permission = get_object_or_404(StoredPermission, pk=item) permission = get_object_or_404(StoredPermission, pk=item)

View File

@@ -11,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _
from .models import UserLocaleProfile from .models import UserLocaleProfile
from .utils import return_attrib from .utils import return_attrib
from .widgets import DetailSelectMultiple, PlainWidget from .widgets import DetailSelectMultiple, DisableableSelectWidget, PlainWidget
class DetailForm(forms.ModelForm): class DetailForm(forms.ModelForm):
@@ -57,24 +57,6 @@ class DetailForm(forms.ModelForm):
self.fields[field_name].widget.attrs.update({'readonly': 'readonly'}) self.fields[field_name].widget.attrs.update({'readonly': 'readonly'})
class GenericAssignRemoveForm(forms.Form):
def __init__(self, *args, **kwargs):
left_list_qryset = kwargs.pop('left_list_qryset', None)
right_list_qryset = kwargs.pop('right_list_qryset', None)
left_filter = kwargs.pop('left_filter', None)
super(GenericAssignRemoveForm, self).__init__(*args, **kwargs)
if left_filter:
self.fields['left_list'].queryset = left_list_qryset.filter(
*left_filter)
else:
self.fields['left_list'].queryset = left_list_qryset
self.fields['right_list'].queryset = right_list_qryset
left_list = forms.ModelMultipleChoiceField(required=False, queryset=None)
right_list = forms.ModelMultipleChoiceField(required=False, queryset=None)
class ChoiceForm(forms.Form): class ChoiceForm(forms.Form):
""" """
Form to be used in side by side templates used to add or remove Form to be used in side by side templates used to add or remove
@@ -83,12 +65,16 @@ class ChoiceForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices', []) choices = kwargs.pop('choices', [])
label = kwargs.pop('label', _('Selection')) label = kwargs.pop('label', _('Selection'))
help_text = kwargs.pop('help_text', None)
disabled_choices = kwargs.pop('disabled_choices', ())
super(ChoiceForm, self).__init__(*args, **kwargs) super(ChoiceForm, self).__init__(*args, **kwargs)
self.fields['selection'].choices = choices self.fields['selection'].choices = choices
self.fields['selection'].label = label self.fields['selection'].label = label
self.fields['selection'].help_text = help_text
self.fields['selection'].widget.disabled_choices = disabled_choices
self.fields['selection'].widget.attrs.update({'size': 14, 'class': 'choice_form'}) self.fields['selection'].widget.attrs.update({'size': 14, 'class': 'choice_form'})
selection = forms.MultipleChoiceField() selection = forms.MultipleChoiceField(widget=DisableableSelectWidget())
class UserForm_view(DetailForm): class UserForm_view(DetailForm):

View File

@@ -33,7 +33,7 @@ from .mixins import (
) )
class AssignRemoveView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, TemplateView): class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, TemplateView):
decode_content_type = False decode_content_type = False
extra_context = None extra_context = None
grouped = False grouped = False
@@ -75,9 +75,15 @@ class AssignRemoveView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, Tem
# Subclass must override # Subclass must override
raise NotImplementedError raise NotImplementedError
def get_disabled_choices(self):
return ()
def get_help_text(self):
return self.help_text
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.unselected_list = ChoiceForm(prefix=self.LEFT_LIST_NAME, choices=self.left_list()) self.unselected_list = ChoiceForm(prefix=self.LEFT_LIST_NAME, choices=self.left_list())
self.selected_list = ChoiceForm(prefix=self.RIGHT_LIST_NAME, choices=self.right_list()) self.selected_list = ChoiceForm(prefix=self.RIGHT_LIST_NAME, choices=self.right_list(), disabled_choices=self.get_disabled_choices(), help_text=self.get_help_text())
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
def process_form(self, prefix, items_function, action_function): def process_form(self, prefix, items_function, action_function):

View File

@@ -98,6 +98,14 @@ class DocumentsApp(MayanAppConfig):
) )
) )
ModelPermission.register_proxy(
source=Document, model=DocumentType,
)
ModelPermission.register_inheritance(
model=Document, related='document_type',
)
SourceColumn(source=Document, label=_('Thumbnail'), attribute=encapsulate(lambda document: document_thumbnail(document, gallery_name='documents:document_list', title=getattr(document, 'label', None), size=setting_thumbnail_size.value))) SourceColumn(source=Document, label=_('Thumbnail'), attribute=encapsulate(lambda document: document_thumbnail(document, gallery_name='documents:document_list', title=getattr(document, 'label', None), size=setting_thumbnail_size.value)))
SourceColumn(source=Document, label=_('Type'), attribute='document_type') SourceColumn(source=Document, label=_('Type'), attribute='document_type')
SourceColumn(source=DeletedDocument, label=_('Type'), attribute='document_type') SourceColumn(source=DeletedDocument, label=_('Type'), attribute='document_type')
@@ -124,7 +132,7 @@ class DocumentsApp(MayanAppConfig):
menu_tools.bind_links(links=[link_clear_image_cache]) menu_tools.bind_links(links=[link_clear_image_cache])
# Document type links # Document type links
menu_object.bind_links(links=[link_document_type_edit, link_document_type_filename_list, link_document_type_delete], sources=[DocumentType]) menu_object.bind_links(links=[link_document_type_edit, link_document_type_filename_list, link_acl_list, link_document_type_delete], sources=[DocumentType])
menu_object.bind_links(links=[link_document_type_filename_edit, link_document_type_filename_delete], sources=[DocumentTypeFilename]) menu_object.bind_links(links=[link_document_type_filename_edit, link_document_type_filename_delete], sources=[DocumentTypeFilename])
menu_secondary.bind_links(links=[link_document_type_list, link_document_type_create], sources=[DocumentType, 'documents:document_type_create', 'documents:document_type_list']) menu_secondary.bind_links(links=[link_document_type_list, link_document_type_create], sources=[DocumentType, 'documents:document_type_create', 'documents:document_type_list'])
menu_sidebar.bind_links(links=[link_document_type_filename_create], sources=[DocumentTypeFilename, 'documents:document_type_filename_list', 'documents:document_type_filename_create']) menu_sidebar.bind_links(links=[link_document_type_filename_create], sources=[DocumentTypeFilename, 'documents:document_type_filename_list', 'documents:document_type_filename_create'])