Merge branch 'feature/hotfixes' of gitlab.com:mayan-edms/mayan-edms into feature/hotfixes
This commit is contained in:
16
mayan/apps/common/api_views.py
Normal file
16
mayan/apps/common/api_views.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from rest_framework import generics
|
||||
|
||||
from .serializers import ContentTypeSerializer
|
||||
|
||||
|
||||
class APIContentTypeList(generics.ListAPIView):
|
||||
"""
|
||||
Returns a list of all the available content types.
|
||||
"""
|
||||
|
||||
serializer_class = ContentTypeSerializer
|
||||
queryset = ContentType.objects.order_by('app_label', 'model')
|
||||
@@ -15,6 +15,7 @@ from django.db.models.signals import post_save
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.celery import app
|
||||
from rest_api.classes import APIEndPoint
|
||||
|
||||
from .classes import Package
|
||||
from .handlers import (
|
||||
@@ -74,6 +75,8 @@ class CommonApp(MayanAppConfig):
|
||||
def ready(self):
|
||||
super(CommonApp, self).ready()
|
||||
|
||||
APIEndPoint(app=self, version_string='1')
|
||||
|
||||
Package(label='Django', license_text='''
|
||||
Copyright (c) Django Software Foundation and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
11
mayan/apps/common/serializers.py
Normal file
11
mayan/apps/common/serializers.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class ContentTypeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
fields = ('app_label', 'id', 'model')
|
||||
model = ContentType
|
||||
@@ -1,5 +1,5 @@
|
||||
def skip_file_descriptor_check(func):
|
||||
def func_wrapper(item):
|
||||
item._skip_file_descriptor_test = True
|
||||
return func(item)
|
||||
return func_wrapper
|
||||
def func_wrapper(item):
|
||||
item._skip_file_descriptor_test = True
|
||||
return func(item)
|
||||
return func_wrapper
|
||||
|
||||
@@ -66,7 +66,7 @@ class TempfileCheckMixin(object):
|
||||
msg='Orphan temporary file. The number of temporary files and/or '
|
||||
'directories at the start and at the end of the test are not the '
|
||||
'same. Orphan entries: {}'.format(
|
||||
','.join(final_temporary_items-self._temporary_items)
|
||||
','.join(final_temporary_items - self._temporary_items)
|
||||
)
|
||||
)
|
||||
super(TempfileCheckMixin, self).tearDown()
|
||||
|
||||
11
mayan/apps/common/tests/test_api.py
Normal file
11
mayan/apps/common/tests/test_api.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
|
||||
class CommonAPITestCase(APITestCase):
|
||||
def test_content_type_list_view(self):
|
||||
response = self.client.get(reverse('rest_api:content-type-list'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -6,7 +6,6 @@ from django.contrib.auth.models import Group
|
||||
from django.core.urlresolvers import clear_url_caches, reverse
|
||||
from django.http import HttpResponse
|
||||
from django.template import Context, Template
|
||||
from django.test import TestCase
|
||||
|
||||
from permissions import Permission
|
||||
from permissions.models import Role
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||
from django.views.generic import RedirectView
|
||||
from django.views.i18n import javascript_catalog
|
||||
|
||||
from api_views import APIContentTypeList
|
||||
from .views import (
|
||||
AboutView, CurrentUserDetailsView, CurrentUserEditView,
|
||||
CurrentUserLocaleProfileDetailsView, CurrentUserLocaleProfileEditView,
|
||||
@@ -66,3 +67,7 @@ urlpatterns += patterns(
|
||||
name='set_language'
|
||||
),
|
||||
)
|
||||
|
||||
api_urls = [
|
||||
url(r'^content_types/$', APIContentTypeList.as_view(), name='content-type-list'),
|
||||
]
|
||||
|
||||
@@ -29,6 +29,7 @@ from events.permissions import permission_events_view
|
||||
from mayan.celery import app
|
||||
from navigation import SourceColumn
|
||||
from rest_api.classes import APIEndPoint
|
||||
from rest_api.fields import DynamicSerializerField
|
||||
from statistics.classes import StatisticNamespace, CharJSLine
|
||||
|
||||
from .handlers import create_default_document_type
|
||||
@@ -94,6 +95,10 @@ class DocumentsApp(MayanAppConfig):
|
||||
DocumentTypeFilename = self.get_model('DocumentTypeFilename')
|
||||
DocumentVersion = self.get_model('DocumentVersion')
|
||||
|
||||
DynamicSerializerField.add_serializer(
|
||||
klass=Document,
|
||||
serializer_class='documents.serializers.DocumentSerializer'
|
||||
)
|
||||
MissingItem(
|
||||
label=_('Create a document type'),
|
||||
description=_(
|
||||
|
||||
@@ -150,6 +150,7 @@ class DocumentSerializer(serializers.HyperlinkedModelSerializer):
|
||||
'latest_version', 'url', 'uuid', 'versions',
|
||||
)
|
||||
model = Document
|
||||
read_only_fields = ('document_type',)
|
||||
|
||||
|
||||
class NewDocumentSerializer(serializers.ModelSerializer):
|
||||
|
||||
72
mayan/apps/events/api_views.py
Normal file
72
mayan/apps/events/api_views.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from actstream.models import Action, any_stream
|
||||
from rest_framework import generics
|
||||
|
||||
from acls.models import AccessControlList
|
||||
from permissions import Permission
|
||||
from rest_api.permissions import MayanPermission
|
||||
|
||||
from .classes import Event
|
||||
from .permissions import permission_events_view
|
||||
from .serializers import EventSerializer, EventTypeSerializer
|
||||
|
||||
|
||||
class APIObjectEventListView(generics.ListAPIView):
|
||||
"""
|
||||
Return a list of events for the specified object.
|
||||
"""
|
||||
|
||||
serializer_class = EventSerializer
|
||||
|
||||
def get_object(self):
|
||||
content_type = get_object_or_404(
|
||||
ContentType, app_label=self.kwargs['app_label'],
|
||||
model=self.kwargs['model']
|
||||
)
|
||||
|
||||
try:
|
||||
return content_type.get_object_for_this_type(
|
||||
pk=self.kwargs['object_id']
|
||||
)
|
||||
except content_type.model_class().DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
def get_queryset(self):
|
||||
object = self.get_object()
|
||||
|
||||
try:
|
||||
Permission.check_permissions(
|
||||
self.request.user, permissions=(permission_events_view,)
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(
|
||||
permission_events_view, self.request.user, object
|
||||
)
|
||||
|
||||
return any_stream(object)
|
||||
|
||||
|
||||
class APIEventTypeListView(generics.ListAPIView):
|
||||
"""
|
||||
Returns a list of all the available event types.
|
||||
"""
|
||||
|
||||
serializer_class = EventTypeSerializer
|
||||
queryset = sorted(Event.all(), key=lambda event: event.name)
|
||||
|
||||
|
||||
class APIEventListView(generics.ListAPIView):
|
||||
"""
|
||||
Returns a list of all the available events.
|
||||
"""
|
||||
|
||||
mayan_view_permissions = {'GET': (permission_events_view,)}
|
||||
permission_classes = (MayanPermission,)
|
||||
queryset = Action.objects.all()
|
||||
serializer_class = EventSerializer
|
||||
@@ -6,8 +6,8 @@ from actstream.models import Action
|
||||
|
||||
from common import MayanAppConfig, menu_tools
|
||||
from common.classes import Package
|
||||
|
||||
from navigation import SourceColumn
|
||||
from rest_api.classes import APIEndPoint
|
||||
|
||||
from .links import link_events_list
|
||||
from .widgets import event_type_link
|
||||
@@ -21,6 +21,8 @@ class EventsApp(MayanAppConfig):
|
||||
def ready(self):
|
||||
super(EventsApp, self).ready()
|
||||
|
||||
APIEndPoint(app=self, version_string='1')
|
||||
|
||||
Package(label='django-activity-stream', license_text='''
|
||||
Copyright (c) 2010-2015, Justin Quick
|
||||
All rights reserved.
|
||||
|
||||
@@ -7,26 +7,48 @@ from actstream import action
|
||||
|
||||
|
||||
class Event(object):
|
||||
_labels = {}
|
||||
_registry = {}
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
return cls._registry.values()
|
||||
|
||||
@classmethod
|
||||
def get(cls, name):
|
||||
try:
|
||||
return cls._registry[name]
|
||||
except KeyError:
|
||||
raise KeyError(
|
||||
_('Unknown or obsolete event type: {0}'.format(name))
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_label(cls, name):
|
||||
try:
|
||||
return cls._labels[name]
|
||||
except KeyError:
|
||||
return _('Unknown or obsolete event type: {0}'.format(name))
|
||||
return cls.get(name=name).label
|
||||
except KeyError as exception:
|
||||
return unicode(exception)
|
||||
|
||||
def __init__(self, name, label):
|
||||
self.name = name
|
||||
self.label = label
|
||||
self.event_type = None
|
||||
self.__class__._labels[name] = label
|
||||
self.__class__._registry[name] = self
|
||||
|
||||
def get_type(self):
|
||||
if not self.event_type:
|
||||
EventType = apps.get_model('events', 'EventType')
|
||||
|
||||
self.event_type, created = EventType.objects.get_or_create(
|
||||
name=self.name
|
||||
)
|
||||
|
||||
return self.event_type
|
||||
|
||||
def commit(self, actor=None, action_object=None, target=None):
|
||||
model = apps.get_model('events', 'EventType')
|
||||
|
||||
if not self.event_type:
|
||||
self.event_type, created = model.objects.get_or_create(
|
||||
EventType = apps.get_model('events', 'EventType')
|
||||
self.event_type, created = EventType.objects.get_or_create(
|
||||
name=self.name
|
||||
)
|
||||
|
||||
|
||||
@@ -13,9 +13,12 @@ class EventType(models.Model):
|
||||
max_length=64, unique=True, verbose_name=_('Name')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return unicode(Event.get_label(self.name))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Event type')
|
||||
verbose_name_plural = _('Event types')
|
||||
|
||||
def __str__(self):
|
||||
return self.get_class().label
|
||||
|
||||
def get_class(self):
|
||||
return Event.get_label(self.name)
|
||||
|
||||
45
mayan/apps/events/serializers.py
Normal file
45
mayan/apps/events/serializers.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.six import string_types
|
||||
|
||||
from actstream.models import Action
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import ContentTypeSerializer
|
||||
from rest_api.fields import DynamicSerializerField
|
||||
|
||||
from .classes import Event
|
||||
from .models import EventType
|
||||
|
||||
|
||||
class EventTypeSerializer(serializers.Serializer):
|
||||
label = serializers.CharField()
|
||||
name = serializers.CharField()
|
||||
|
||||
def to_representation(self, instance):
|
||||
if isinstance(instance, Event):
|
||||
return super(EventTypeSerializer, self).to_representation(
|
||||
instance
|
||||
)
|
||||
elif isinstance(instance, EventType):
|
||||
return super(EventTypeSerializer, self).to_representation(
|
||||
instance.get_class()
|
||||
)
|
||||
elif isinstance(instance, string_types):
|
||||
return super(EventTypeSerializer, self).to_representation(
|
||||
Event.get(name=instance)
|
||||
)
|
||||
|
||||
|
||||
class EventSerializer(serializers.ModelSerializer):
|
||||
actor = DynamicSerializerField(read_only=True)
|
||||
target = DynamicSerializerField(read_only=True)
|
||||
actor_content_type = ContentTypeSerializer(read_only=True)
|
||||
target_content_type = ContentTypeSerializer(read_only=True)
|
||||
verb = EventTypeSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
exclude = (
|
||||
'action_object_content_type', 'action_object_object_id'
|
||||
)
|
||||
model = Action
|
||||
11
mayan/apps/events/tests/test_api.py
Normal file
11
mayan/apps/events/tests/test_api.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
|
||||
class EventAPITestCase(APITestCase):
|
||||
def test_evet_type_list_view(self):
|
||||
response = self.client.get(reverse('rest_api:event-type-list'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -2,7 +2,6 @@ from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from acls.models import AccessControlList
|
||||
from documents.tests.test_views import GenericDocumentViewTestCase
|
||||
from user_management.tests import (
|
||||
TEST_USER_USERNAME, TEST_USER_PASSWORD
|
||||
|
||||
@@ -2,6 +2,9 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from .api_views import (
|
||||
APIEventListView, APIEventTypeListView, APIObjectEventListView
|
||||
)
|
||||
from .views import EventListView, ObjectEventListView, VerbEventListView
|
||||
|
||||
urlpatterns = patterns(
|
||||
@@ -16,3 +19,12 @@ urlpatterns = patterns(
|
||||
name='events_by_verb'
|
||||
),
|
||||
)
|
||||
|
||||
api_urls = [
|
||||
url(r'^types/$', APIEventTypeListView.as_view(), name='event-type-list'),
|
||||
url(r'^events/$', APIEventListView.as_view(), name='event-list'),
|
||||
url(
|
||||
r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/events/$',
|
||||
APIObjectEventListView.as_view(), name='object-event-list'
|
||||
),
|
||||
]
|
||||
|
||||
@@ -5,22 +5,35 @@ from rest_framework import generics
|
||||
from rest_api.filters import MayanObjectPermissionsFilter
|
||||
from rest_api.permissions import MayanPermission
|
||||
|
||||
from .classes import Permission
|
||||
from .models import Role
|
||||
from .permissions import (
|
||||
permission_role_create, permission_role_delete, permission_role_edit,
|
||||
permission_role_view
|
||||
)
|
||||
from .serializers import RoleSerializer
|
||||
from .serializers import (
|
||||
PermissionSerializer, RoleSerializer, WritableRoleSerializer
|
||||
)
|
||||
|
||||
|
||||
class APIPermissionList(generics.ListAPIView):
|
||||
serializer_class = PermissionSerializer
|
||||
queryset = Permission.all()
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
Returns a list of all the available permissions.
|
||||
"""
|
||||
|
||||
return super(APIPermissionList, self).get(*args, **kwargs)
|
||||
|
||||
|
||||
class APIRoleListView(generics.ListCreateAPIView):
|
||||
serializer_class = RoleSerializer
|
||||
queryset = Role.objects.all()
|
||||
|
||||
permission_classes = (MayanPermission,)
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {'GET': (permission_role_view,)}
|
||||
mayan_view_permissions = {'POST': (permission_role_create,)}
|
||||
permission_classes = (MayanPermission,)
|
||||
queryset = Role.objects.all()
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
@@ -29,6 +42,12 @@ class APIRoleListView(generics.ListCreateAPIView):
|
||||
|
||||
return super(APIRoleListView, self).get(*args, **kwargs)
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.method == 'GET':
|
||||
return RoleSerializer
|
||||
elif self.request.method == 'POST':
|
||||
return WritableRoleSerializer
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
"""
|
||||
Create a new role.
|
||||
@@ -38,16 +57,14 @@ class APIRoleListView(generics.ListCreateAPIView):
|
||||
|
||||
|
||||
class APIRoleView(generics.RetrieveUpdateDestroyAPIView):
|
||||
serializer_class = RoleSerializer
|
||||
queryset = Role.objects.all()
|
||||
|
||||
permission_classes = (MayanPermission,)
|
||||
mayan_object_permissions = {
|
||||
'GET': (permission_role_view,),
|
||||
'PUT': (permission_role_edit,),
|
||||
'PATCH': (permission_role_edit,),
|
||||
'DELETE': (permission_role_delete,)
|
||||
}
|
||||
permission_classes = (MayanPermission,)
|
||||
queryset = Role.objects.all()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""
|
||||
@@ -63,6 +80,12 @@ class APIRoleView(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
||||
return super(APIRoleView, self).get(*args, **kwargs)
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.method == 'GET':
|
||||
return RoleSerializer
|
||||
elif self.request.method in ('PATCH', 'PUT'):
|
||||
return WritableRoleSerializer
|
||||
|
||||
def patch(self, *args, **kwargs):
|
||||
"""
|
||||
Edit the selected role.
|
||||
|
||||
@@ -45,12 +45,15 @@ class PermissionNamespace(object):
|
||||
|
||||
|
||||
class Permission(object):
|
||||
_stored_permissions_cache = {}
|
||||
_permissions = {}
|
||||
_stored_permissions_cache = {}
|
||||
|
||||
@classmethod
|
||||
def invalidate_cache(cls):
|
||||
cls._stored_permissions_cache = {}
|
||||
def all(cls):
|
||||
# Return sorted permisions by namespace.name
|
||||
return sorted(
|
||||
cls._permissions.values(), key=lambda x: x.namespace.name
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_permissions(cls, requester, permissions):
|
||||
@@ -62,6 +65,14 @@ class Permission(object):
|
||||
|
||||
raise PermissionDenied(_('Insufficient permissions.'))
|
||||
|
||||
@classmethod
|
||||
def get(cls, get_dict, proxy_only=False):
|
||||
if 'pk' in get_dict:
|
||||
if proxy_only:
|
||||
return cls._permissions[get_dict['pk']]
|
||||
else:
|
||||
return cls._permissions[get_dict['pk']].stored_permission
|
||||
|
||||
@classmethod
|
||||
def get_for_holder(cls, holder):
|
||||
StoredPermission = apps.get_model(
|
||||
@@ -71,19 +82,8 @@ class Permission(object):
|
||||
return StoredPermission.get_for_holder(holder)
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
# Return sorted permisions by namespace.name
|
||||
return sorted(
|
||||
cls._permissions.values(), key=lambda x: x.namespace.name
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get(cls, get_dict, proxy_only=False):
|
||||
if 'pk' in get_dict:
|
||||
if proxy_only:
|
||||
return cls._permissions[get_dict['pk']]
|
||||
else:
|
||||
return cls._permissions[get_dict['pk']].stored_permission
|
||||
def invalidate_cache(cls):
|
||||
cls._stored_permissions_cache = {}
|
||||
|
||||
def __init__(self, namespace, name, label):
|
||||
self.namespace = namespace
|
||||
@@ -92,16 +92,15 @@ class Permission(object):
|
||||
self.pk = self.uuid
|
||||
self.__class__._permissions[self.uuid] = self
|
||||
|
||||
def __repr__(self):
|
||||
return self.pk
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.label)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
return '%s.%s' % (self.namespace.name, self.name)
|
||||
|
||||
@property
|
||||
def stored_permission(self):
|
||||
StoredPermission = apps.get_model(
|
||||
@@ -120,3 +119,7 @@ class Permission(object):
|
||||
self.uuid
|
||||
] = stored_permission
|
||||
return stored_permission
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
return '%s.%s' % (self.namespace.name, self.name)
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .classes import Permission
|
||||
from .managers import RoleManager, StoredPermissionManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -21,8 +22,6 @@ class StoredPermission(models.Model):
|
||||
objects = StoredPermissionManager()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
from .classes import Permission
|
||||
|
||||
super(StoredPermission, self).__init__(*args, **kwargs)
|
||||
try:
|
||||
self.volatile_permission = Permission.get(
|
||||
|
||||
@@ -1,11 +1,110 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from .models import Role
|
||||
from user_management.serializers import GroupSerializer
|
||||
|
||||
from .classes import Permission
|
||||
from .models import Role, StoredPermission
|
||||
|
||||
|
||||
class RoleSerializer(serializers.ModelSerializer):
|
||||
class PermissionSerializer(serializers.Serializer):
|
||||
namespace = serializers.CharField()
|
||||
pk = serializers.CharField()
|
||||
label = serializers.CharField()
|
||||
|
||||
def to_representation(self, instance):
|
||||
if isinstance(instance, StoredPermission):
|
||||
return super(PermissionSerializer, self).to_representation(
|
||||
instance.volatile_permission
|
||||
)
|
||||
else:
|
||||
return super(PermissionSerializer, self).to_representation(
|
||||
instance
|
||||
)
|
||||
|
||||
|
||||
class RoleSerializer(serializers.HyperlinkedModelSerializer):
|
||||
groups = GroupSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
fields = ('id', 'label')
|
||||
fields = ('groups', 'id', 'label')
|
||||
model = Role
|
||||
|
||||
|
||||
class WritableRoleSerializer(serializers.HyperlinkedModelSerializer):
|
||||
groups_pk_list = serializers.CharField(
|
||||
help_text=_(
|
||||
'Comma separated list of groups primary keys to add to, or replace'
|
||||
' in this role.'
|
||||
), required=False
|
||||
)
|
||||
|
||||
permissions_pk_list = serializers.CharField(
|
||||
help_text=_(
|
||||
'Comma separated list of permission primary keys to grant to this '
|
||||
'role.'
|
||||
), required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = ('groups_pk_list', 'id', 'label', 'permissions_pk_list')
|
||||
model = Role
|
||||
|
||||
def create(self, validated_data):
|
||||
result = validated_data.copy()
|
||||
|
||||
self.groups_pk_list = validated_data.pop('groups_pk_list', '')
|
||||
self.permissions_pk_list = validated_data.pop(
|
||||
'permissions_pk_list', ''
|
||||
)
|
||||
|
||||
instance = super(WritableRoleSerializer, self).create(validated_data)
|
||||
|
||||
if self.groups_pk_list:
|
||||
self._add_groups(instance=instance)
|
||||
|
||||
if self.permissions_pk_list:
|
||||
self._add_permissions(instance=instance)
|
||||
|
||||
return result
|
||||
|
||||
def _add_groups(self, instance):
|
||||
instance.groups.add(
|
||||
*Group.objects.filter(pk__in=self.groups_pk_list.split(','))
|
||||
)
|
||||
|
||||
def _add_permissions(self, instance):
|
||||
for pk in self.permissions_pk_list.split(','):
|
||||
try:
|
||||
stored_permission = Permission.get(get_dict={'pk': pk})
|
||||
instance.permissions.add(stored_permission)
|
||||
instance.save()
|
||||
except KeyError:
|
||||
raise ValidationError(_('No such permission: %s') % pk)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
result = validated_data.copy()
|
||||
|
||||
self.groups_pk_list = validated_data.pop('groups_pk_list', '')
|
||||
self.permissions_pk_list = validated_data.pop(
|
||||
'permissions_pk_list', ''
|
||||
)
|
||||
|
||||
result = super(WritableRoleSerializer, self).update(
|
||||
instance, validated_data
|
||||
)
|
||||
|
||||
if self.groups_pk_list:
|
||||
instance.groups.clear()
|
||||
self._add_groups(instance=instance)
|
||||
|
||||
if self.permissions_pk_list:
|
||||
instance.permissions.clear()
|
||||
self._add_permissions(instance=instance)
|
||||
|
||||
return result
|
||||
|
||||
165
mayan/apps/permissions/tests/test_api.py
Normal file
165
mayan/apps/permissions/tests/test_api.py
Normal file
@@ -0,0 +1,165 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from user_management.tests.literals import (
|
||||
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_GROUP
|
||||
)
|
||||
|
||||
from ..classes import Permission
|
||||
from ..models import Role
|
||||
from ..permissions import permission_role_view
|
||||
|
||||
from .literals import TEST_ROLE_LABEL, TEST_ROLE_LABEL_EDITED
|
||||
|
||||
|
||||
class PermissionAPITestCase(APITestCase):
|
||||
def setUp(self):
|
||||
super(PermissionAPITestCase, self).setUp()
|
||||
self.admin_user = get_user_model().objects.create_superuser(
|
||||
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
|
||||
password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
|
||||
self.client.login(
|
||||
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
|
||||
Permission.invalidate_cache()
|
||||
|
||||
def test_permissions_list_view(self):
|
||||
response = self.client.get(reverse('rest_api:permission-list'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def _create_role(self):
|
||||
self.role = Role.objects.create(label=TEST_ROLE_LABEL)
|
||||
|
||||
def test_roles_list_view(self):
|
||||
self._create_role()
|
||||
|
||||
response = self.client.get(reverse('rest_api:role-list'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['results'][0]['label'], TEST_ROLE_LABEL)
|
||||
|
||||
def _role_create_request(self, extra_data=None):
|
||||
data = {
|
||||
'label': TEST_ROLE_LABEL
|
||||
}
|
||||
|
||||
if extra_data:
|
||||
data.update(extra_data)
|
||||
|
||||
return self.client.post(
|
||||
reverse('rest_api:role-list'), data=data
|
||||
)
|
||||
|
||||
def test_role_create_view(self):
|
||||
response = self._role_create_request()
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(Role.objects.count(), 1)
|
||||
self.assertEqual(Role.objects.first().label, TEST_ROLE_LABEL)
|
||||
|
||||
def _create_group(self):
|
||||
self.group = Group.objects.create(name=TEST_GROUP)
|
||||
|
||||
def test_role_create_complex_view(self):
|
||||
self._create_group()
|
||||
|
||||
response = self._role_create_request(
|
||||
extra_data={
|
||||
'groups_pk_list': '{}'.format(self.group.pk),
|
||||
'permissions_pk_list': '{}'.format(permission_role_view.pk)
|
||||
}
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(Role.objects.count(), 1)
|
||||
self.assertEqual(Role.objects.first().label, TEST_ROLE_LABEL)
|
||||
self.assertQuerysetEqual(
|
||||
Role.objects.first().groups.all(), (repr(self.group),)
|
||||
)
|
||||
self.assertQuerysetEqual(
|
||||
Role.objects.first().permissions.all(),
|
||||
(repr(permission_role_view.stored_permission),)
|
||||
)
|
||||
|
||||
def _role_edit_request(self, extra_data=None, request_type='patch'):
|
||||
data = {
|
||||
'label': TEST_ROLE_LABEL_EDITED
|
||||
}
|
||||
|
||||
if extra_data:
|
||||
data.update(extra_data)
|
||||
|
||||
return getattr(self.client, request_type)(
|
||||
reverse('rest_api:role-detail', args=(self.role.pk,)), data=data
|
||||
)
|
||||
|
||||
def test_role_edit_via_patch(self):
|
||||
self._create_role()
|
||||
response = self._role_edit_request()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(Role.objects.first().label, TEST_ROLE_LABEL_EDITED)
|
||||
|
||||
def test_role_edit_complex_via_patch(self):
|
||||
Role.objects.all().delete()
|
||||
Group.objects.all().delete()
|
||||
|
||||
self._create_role()
|
||||
self._create_group()
|
||||
|
||||
response = self._role_edit_request(
|
||||
extra_data={
|
||||
'groups_pk_list': '{}'.format(self.group.pk),
|
||||
'permissions_pk_list': '{}'.format(permission_role_view.pk)
|
||||
}
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(Role.objects.first().label, TEST_ROLE_LABEL_EDITED)
|
||||
self.assertQuerysetEqual(
|
||||
Role.objects.first().groups.all(), (repr(self.group),)
|
||||
)
|
||||
self.assertQuerysetEqual(
|
||||
Role.objects.first().permissions.all(),
|
||||
(repr(permission_role_view.stored_permission),)
|
||||
)
|
||||
|
||||
def test_role_edit_via_put(self):
|
||||
self._create_role()
|
||||
response = self._role_edit_request(request_type='put')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(Role.objects.first().label, TEST_ROLE_LABEL_EDITED)
|
||||
|
||||
def test_role_edit_complex_via_put(self):
|
||||
Role.objects.all().delete()
|
||||
Group.objects.all().delete()
|
||||
|
||||
self._create_role()
|
||||
self._create_group()
|
||||
|
||||
response = self._role_edit_request(
|
||||
extra_data={
|
||||
'groups_pk_list': '{}'.format(self.group.pk),
|
||||
'permissions_pk_list': '{}'.format(permission_role_view.pk)
|
||||
}, request_type='put'
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(Role.objects.first().label, TEST_ROLE_LABEL_EDITED)
|
||||
self.assertQuerysetEqual(
|
||||
Role.objects.first().groups.all(), (repr(self.group),)
|
||||
)
|
||||
self.assertQuerysetEqual(
|
||||
Role.objects.first().permissions.all(),
|
||||
(repr(permission_role_view.stored_permission),)
|
||||
)
|
||||
|
||||
def test_role_delete_view(self):
|
||||
self._create_role()
|
||||
response = self.client.delete(
|
||||
reverse('rest_api:role-detail', args=(self.role.pk,))
|
||||
)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertEqual(Role.objects.count(), 0)
|
||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from .api_views import APIRoleListView, APIRoleView
|
||||
from .api_views import APIPermissionList, APIRoleListView, APIRoleView
|
||||
from .views import (
|
||||
RoleCreateView, RoleDeleteView, RoleEditView, RoleListView,
|
||||
SetupRoleMembersView, SetupRolePermissionsView
|
||||
@@ -29,6 +29,7 @@ urlpatterns = patterns(
|
||||
|
||||
api_urls = patterns(
|
||||
'',
|
||||
url(r'^permissions/$', APIPermissionList.as_view(), name='permission-list'),
|
||||
url(r'^roles/$', APIRoleListView.as_view(), name='role-list'),
|
||||
url(r'^roles/(?P<pk>[0-9]+)/$', APIRoleView.as_view(), name='role-detail'),
|
||||
)
|
||||
|
||||
30
mayan/apps/rest_api/fields.py
Normal file
30
mayan/apps/rest_api/fields.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.six import string_types
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class DynamicSerializerField(serializers.ReadOnlyField):
|
||||
serializers = {}
|
||||
|
||||
@classmethod
|
||||
def add_serializer(cls, klass, serializer_class):
|
||||
if isinstance(klass, string_types):
|
||||
klass = import_string(klass)
|
||||
|
||||
if isinstance(serializer_class, string_types):
|
||||
serializer_class = import_string(serializer_class)
|
||||
|
||||
cls.serializers[klass] = serializer_class
|
||||
|
||||
def to_representation(self, value):
|
||||
for klass, serializer_class in self.serializers.items():
|
||||
if isinstance(value, klass):
|
||||
return serializer_class(
|
||||
context={'request': self.context['request']}
|
||||
).to_representation(instance=value)
|
||||
|
||||
return _('Unable to find serializer class for: %s') % value
|
||||
@@ -12,6 +12,7 @@ from common.widgets import two_state_template
|
||||
from metadata import MetadataLookup
|
||||
from navigation import SourceColumn
|
||||
from rest_api.classes import APIEndPoint
|
||||
from rest_api.fields import DynamicSerializerField
|
||||
|
||||
from .links import (
|
||||
link_group_add, link_group_delete, link_group_edit, link_group_list,
|
||||
@@ -42,6 +43,10 @@ class UserManagementApp(MayanAppConfig):
|
||||
User = get_user_model()
|
||||
|
||||
APIEndPoint(app=self, version_string='1')
|
||||
DynamicSerializerField.add_serializer(
|
||||
klass=get_user_model(),
|
||||
serializer_class='user_management.serializers.UserSerializer'
|
||||
)
|
||||
|
||||
MetadataLookup(
|
||||
description=_('All the groups.'), name='groups',
|
||||
|
||||
Reference in New Issue
Block a user