Add permission inheritance by parent object. Add ACLs app model tests.
This commit is contained in:
@@ -3,7 +3,7 @@ python:
|
||||
- 2.7
|
||||
env:
|
||||
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:
|
||||
- DB=mysql
|
||||
- DB=postgres
|
||||
|
||||
@@ -9,6 +9,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class ModelPermission(object):
|
||||
_registry = {}
|
||||
_proxies = {}
|
||||
_inheritances = {}
|
||||
|
||||
@classmethod
|
||||
def register(cls, model, permissions):
|
||||
@@ -18,6 +20,25 @@ class ModelPermission(object):
|
||||
|
||||
@classmethod
|
||||
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]
|
||||
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]
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
from __future__ import unicode_literals
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext
|
||||
|
||||
from permissions.models import StoredPermission
|
||||
|
||||
from .classes import ModelPermission
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -16,20 +21,44 @@ class AccessControlListManager(models.Manager):
|
||||
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):
|
||||
if user.is_superuser or user.is_staff:
|
||||
return True
|
||||
|
||||
user_roles = []
|
||||
for group in user.groups.all():
|
||||
for role in group.roles.all():
|
||||
user_roles.append(role)
|
||||
|
||||
try:
|
||||
stored_permissions = [permission.stored_permission for permission in permissions]
|
||||
except TypeError:
|
||||
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):
|
||||
raise PermissionDenied(ugettext('Insufficient access.'))
|
||||
|
||||
@@ -42,11 +71,21 @@ class AccessControlListManager(models.Manager):
|
||||
for role in group.roles.all():
|
||||
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)
|
||||
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(pk__in=acls)
|
||||
new_queryset = queryset.filter(parent_acl_query | acl_query)
|
||||
|
||||
if new_queryset.count() == 0 and exception_on_empty:
|
||||
raise PermissionDenied
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from permissions.models import Role, StoredPermission
|
||||
|
||||
from .classes import ModelPermission
|
||||
from .managers import AccessControlListManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -43,3 +44,6 @@ class AccessControlList(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
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)
|
||||
|
||||
146
mayan/apps/acls/test_models.py
Normal file
146
mayan/apps/acls/test_models.py
Normal 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)
|
||||
@@ -5,6 +5,7 @@ import logging
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
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__)
|
||||
|
||||
|
||||
def _permission_titles(permission_list):
|
||||
return ', '.join([unicode(permission) for permission in permission_list])
|
||||
|
||||
|
||||
class ACLListView(SingleObjectListView):
|
||||
@staticmethod
|
||||
def permission_titles(permission_list):
|
||||
return ', '.join([unicode(permission) for permission in permission_list])
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
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):
|
||||
return AccessControlList.objects.filter(content_type=self.content_type, object_id=self.content_object.pk)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ACLListView, self).get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'hide_object': True,
|
||||
'object': self.content_object,
|
||||
'title': _('Access control lists for: %s' % self.content_object),
|
||||
'extra_columns': [
|
||||
{
|
||||
'name': _('Role'),
|
||||
'attribute': 'role'
|
||||
},
|
||||
{
|
||||
'name': _('Permissions'),
|
||||
'attribute': encapsulate(lambda x: _permission_titles(x.permissions.all()))
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'hide_object': True,
|
||||
'object': self.content_object,
|
||||
'title': _('Access control lists for: %s' % self.content_object),
|
||||
'extra_columns': [
|
||||
{
|
||||
'name': _('Role'),
|
||||
'attribute': 'role'
|
||||
},
|
||||
{
|
||||
'name': _('Permissions'),
|
||||
'attribute': encapsulate(lambda entry: ACLListView.permission_titles(entry.permissions.all()))
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class ACLCreateView(SingleObjectCreateView):
|
||||
model = AccessControlList
|
||||
fields = ('role',)
|
||||
model = AccessControlList
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
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)
|
||||
|
||||
def form_valid(self, form):
|
||||
instance = form.save(commit=False)
|
||||
instance.content_object = self.content_object
|
||||
instance.save()
|
||||
self.instance = form.save(commit=False)
|
||||
self.instance.content_object = self.content_object
|
||||
self.instance.save()
|
||||
|
||||
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):
|
||||
return {
|
||||
'object': self.content_object,
|
||||
@@ -105,7 +104,16 @@ class ACLCreateView(SingleObjectCreateView):
|
||||
|
||||
class ACLDeleteView(SingleObjectDeleteView):
|
||||
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):
|
||||
context = super(ACLDeleteView, self).get_context_data(**kwargs)
|
||||
@@ -120,45 +128,74 @@ class ACLDeleteView(SingleObjectDeleteView):
|
||||
|
||||
class ACLPermissionsView(AssignRemoveView):
|
||||
grouped = True
|
||||
object_permission = permission_acl_edit
|
||||
left_list_title = _('Available 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):
|
||||
permission = get_object_or_404(StoredPermission, pk=item)
|
||||
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):
|
||||
return get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
|
||||
|
||||
def left_list(self):
|
||||
results = []
|
||||
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))
|
||||
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))
|
||||
|
||||
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):
|
||||
results = []
|
||||
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
|
||||
return ACLPermissionsView.generate_choices(self.get_granted_list())
|
||||
|
||||
def remove(self, item):
|
||||
permission = get_object_or_404(StoredPermission, pk=item)
|
||||
|
||||
@@ -11,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .models import UserLocaleProfile
|
||||
from .utils import return_attrib
|
||||
from .widgets import DetailSelectMultiple, PlainWidget
|
||||
from .widgets import DetailSelectMultiple, DisableableSelectWidget, PlainWidget
|
||||
|
||||
|
||||
class DetailForm(forms.ModelForm):
|
||||
@@ -57,24 +57,6 @@ class DetailForm(forms.ModelForm):
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
choices = kwargs.pop('choices', [])
|
||||
label = kwargs.pop('label', _('Selection'))
|
||||
help_text = kwargs.pop('help_text', None)
|
||||
disabled_choices = kwargs.pop('disabled_choices', ())
|
||||
super(ChoiceForm, self).__init__(*args, **kwargs)
|
||||
self.fields['selection'].choices = choices
|
||||
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'})
|
||||
|
||||
selection = forms.MultipleChoiceField()
|
||||
selection = forms.MultipleChoiceField(widget=DisableableSelectWidget())
|
||||
|
||||
|
||||
class UserForm_view(DetailForm):
|
||||
|
||||
@@ -33,7 +33,7 @@ from .mixins import (
|
||||
)
|
||||
|
||||
|
||||
class AssignRemoveView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, TemplateView):
|
||||
class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, TemplateView):
|
||||
decode_content_type = False
|
||||
extra_context = None
|
||||
grouped = False
|
||||
@@ -75,9 +75,15 @@ class AssignRemoveView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, Tem
|
||||
# Subclass must override
|
||||
raise NotImplementedError
|
||||
|
||||
def get_disabled_choices(self):
|
||||
return ()
|
||||
|
||||
def get_help_text(self):
|
||||
return self.help_text
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
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())
|
||||
|
||||
def process_form(self, prefix, items_function, action_function):
|
||||
|
||||
@@ -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=_('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])
|
||||
|
||||
# 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_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'])
|
||||
|
||||
Reference in New Issue
Block a user