diff --git a/mayan/apps/linking/api_views.py b/mayan/apps/linking/api_views.py new file mode 100644 index 0000000000..931ea56119 --- /dev/null +++ b/mayan/apps/linking/api_views.py @@ -0,0 +1,196 @@ +from __future__ import absolute_import, unicode_literals + +from django.core.exceptions import PermissionDenied +from django.shortcuts import get_object_or_404 + +from rest_framework import generics + +from acls.models import AccessControlList +from permissions import Permission +from rest_api.filters import MayanObjectPermissionsFilter +from rest_api.permissions import MayanPermission + +from .models import SmartLink +from .permissions import ( + permission_smart_link_create, permission_smart_link_delete, + permission_smart_link_edit, permission_smart_link_view +) +from .serializers import SmartLinkConditionSerializer, SmartLinkSerializer + + +class APISmartLinkConditionListView(generics.ListCreateAPIView): + serializer_class = SmartLinkConditionSerializer + + def get(self, *args, **kwargs): + """ + Returns a list of all the smart link conditions. + """ + return super(APISmartLinkConditionListView, self).get(*args, **kwargs) + + def get_queryset(self): + return self.get_smart_link().conditions.all() + + def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ + return { + 'format': self.format_kwarg, + 'request': self.request, + 'smart_link': self.get_smart_link(), + 'view': self + } + + def get_smart_link(self): + if self.request.method == 'GET': + permission_required = permission_smart_link_view + else: + permission_required = permission_smart_link_edit + + smart_link = get_object_or_404(SmartLink, pk=self.kwargs['pk']) + + try: + Permission.check_permissions( + self.request.user, (permission_required,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_required, self.request.user, smart_link + ) + + return smart_link + + def post(self, *args, **kwargs): + """ + Create a new smart link condition. + """ + return super(APISmartLinkConditionListView, self).post(*args, **kwargs) + + +class APISmartLinkConditionView(generics.RetrieveUpdateDestroyAPIView): + lookup_url_kwarg = 'condition_pk' + serializer_class = SmartLinkConditionSerializer + + def delete(self, *args, **kwargs): + """ + Delete the selected smart link condition. + """ + + return super(APISmartLinkConditionView, self).delete(*args, **kwargs) + + def get(self, *args, **kwargs): + """ + Return the details of the selected smart link condition. + """ + + return super(APISmartLinkConditionView, self).get(*args, **kwargs) + + def get_queryset(self): + return self.get_smart_link().conditions.all() + + def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ + return { + 'format': self.format_kwarg, + 'request': self.request, + 'smart_link': self.get_smart_link(), + 'view': self + } + + def get_smart_link(self): + if self.request.method == 'GET': + permission_required = permission_smart_link_view + else: + permission_required = permission_smart_link_edit + + smart_link = get_object_or_404(SmartLink, pk=self.kwargs['pk']) + + try: + Permission.check_permissions( + self.request.user, (permission_required,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_required, self.request.user, smart_link + ) + + return smart_link + + def patch(self, *args, **kwargs): + """ + Edit the selected smart link condition. + """ + + return super(APISmartLinkConditionView, self).patch(*args, **kwargs) + + def put(self, *args, **kwargs): + """ + Edit the selected smart link condition. + """ + + return super(APISmartLinkConditionView, self).put(*args, **kwargs) + + +class APISmartLinkListView(generics.ListCreateAPIView): + filter_backends = (MayanObjectPermissionsFilter,) + mayan_object_permissions = {'GET': (permission_smart_link_view,)} + mayan_view_permissions = {'POST': (permission_smart_link_create,)} + permission_classes = (MayanPermission,) + queryset = SmartLink.objects.all() + serializer_class = SmartLinkSerializer + + def get(self, *args, **kwargs): + """ + Returns a list of all the smart links. + """ + + return super(APISmartLinkListView, self).get(*args, **kwargs) + + def post(self, *args, **kwargs): + """ + Create a new smart link. + """ + + return super(APISmartLinkListView, self).post(*args, **kwargs) + + +class APISmartLinkView(generics.RetrieveUpdateDestroyAPIView): + filter_backends = (MayanObjectPermissionsFilter,) + mayan_object_permissions = { + 'DELETE': (permission_smart_link_delete,), + 'GET': (permission_smart_link_view,), + 'PATCH': (permission_smart_link_edit,), + 'PUT': (permission_smart_link_edit,) + } + queryset = SmartLink.objects.all() + serializer_class = SmartLinkSerializer + + def delete(self, *args, **kwargs): + """ + Delete the selected smart link. + """ + + return super(APISmartLinkView, self).delete(*args, **kwargs) + + def get(self, *args, **kwargs): + """ + Return the details of the selected smart ink. + """ + + return super(APISmartLinkView, self).get(*args, **kwargs) + + def patch(self, *args, **kwargs): + """ + Edit the selected smart link. + """ + + return super(APISmartLinkView, self).patch(*args, **kwargs) + + def put(self, *args, **kwargs): + """ + Edit the selected smart link. + """ + + return super(APISmartLinkView, self).put(*args, **kwargs) diff --git a/mayan/apps/linking/apps.py b/mayan/apps/linking/apps.py index 54250c85e4..41eb9df8cc 100644 --- a/mayan/apps/linking/apps.py +++ b/mayan/apps/linking/apps.py @@ -12,6 +12,7 @@ from common import ( ) from common.widgets import two_state_template from navigation import SourceColumn +from rest_api.classes import APIEndPoint from .links import ( link_smart_link_create, link_smart_link_condition_create, @@ -35,6 +36,8 @@ class LinkingApp(MayanAppConfig): def ready(self): super(LinkingApp, self).ready() + APIEndPoint(app=self, version_string='1') + Document = apps.get_model( app_label='documents', model_name='Document' ) diff --git a/mayan/apps/linking/serializers.py b/mayan/apps/linking/serializers.py new file mode 100644 index 0000000000..4e94a3dc36 --- /dev/null +++ b/mayan/apps/linking/serializers.py @@ -0,0 +1,50 @@ +from __future__ import absolute_import, unicode_literals + +from rest_framework import serializers +from rest_framework.reverse import reverse + +from .models import SmartLink, SmartLinkCondition + + +class SmartLinkConditionSerializer(serializers.HyperlinkedModelSerializer): + smart_link_url = serializers.SerializerMethodField() + url = serializers.SerializerMethodField() + + class Meta: + fields = ( + 'enabled', 'expression', 'foreign_document_data', 'inclusion', + 'id', 'negated', 'operator', 'smart_link_url', 'url' + ) + model = SmartLinkCondition + + def create(self, validated_data): + validated_data['smart_link'] = self.context['smart_link'] + return super(SmartLinkConditionSerializer, self).create(validated_data) + + def get_smart_link_url(self, instance): + return reverse( + 'rest_api:smartlink-detail', args=(instance.smart_link.pk,), + request=self.context['request'], format=self.context['format'] + ) + + def get_url(self, instance): + return reverse( + 'rest_api:smartlinkcondition-detail', args=( + instance.smart_link.pk, instance.pk, + ), request=self.context['request'], format=self.context['format'] + ) + + +class SmartLinkSerializer(serializers.HyperlinkedModelSerializer): + conditions_url = serializers.HyperlinkedIdentityField( + view_name='rest_api:smartlinkcondition-list' + ) + + class Meta: + extra_kwargs = { + 'url': {'view_name': 'rest_api:smartlink-detail'}, + } + fields = ( + 'conditions_url', 'dynamic_label', 'enabled', 'label', 'id', 'url' + ) + model = SmartLink diff --git a/mayan/apps/linking/tests/literals.py b/mayan/apps/linking/tests/literals.py index 279ea65564..e527654e91 100644 --- a/mayan/apps/linking/tests/literals.py +++ b/mayan/apps/linking/tests/literals.py @@ -1,5 +1,9 @@ from __future__ import unicode_literals +TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA = 'label' +TEST_SMART_LINK_CONDITION_EXPRESSION = '\'test\'' +TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED = '\'test edited\'' +TEST_SMART_LINK_CONDITION_OPERATOR = 'icontains' TEST_SMART_LINK_DYNAMIC_LABEL = '{{ document.label }}' -TEST_SMART_LINK_EDITED_LABEL = 'test edited label' +TEST_SMART_LINK_LABEL_EDITED = 'test edited label' TEST_SMART_LINK_LABEL = 'test label' diff --git a/mayan/apps/linking/tests/test_api.py b/mayan/apps/linking/tests/test_api.py new file mode 100644 index 0000000000..8f334b483b --- /dev/null +++ b/mayan/apps/linking/tests/test_api.py @@ -0,0 +1,265 @@ +from __future__ import unicode_literals + +from django.contrib.auth import get_user_model +from django.core.urlresolvers import reverse +from django.test import override_settings + +from rest_framework.test import APITestCase + +from documents.models import DocumentType +from documents.tests.literals import ( + TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH +) +from user_management.tests.literals import ( + TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME +) + +from ..models import SmartLink, SmartLinkCondition + +from .literals import ( + TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA, + TEST_SMART_LINK_CONDITION_EXPRESSION, + TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED, + TEST_SMART_LINK_CONDITION_OPERATOR, TEST_SMART_LINK_DYNAMIC_LABEL, + TEST_SMART_LINK_LABEL_EDITED, TEST_SMART_LINK_LABEL +) + + +@override_settings(OCR_AUTO_OCR=False) +class SmartLinkAPITestCase(APITestCase): + def setUp(self): + 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 + ) + + def tearDown(self): + if hasattr(self, 'document_type'): + self.document_type.delete() + + def _create_document_type(self): + self.document_type = DocumentType.objects.create( + label=TEST_DOCUMENT_TYPE + ) + + def _create_document(self): + with open(TEST_SMALL_DOCUMENT_PATH) as file_object: + self.document = self.document_type.new_document( + file_object=file_object + ) + + def _create_smart_link(self): + return SmartLink.objects.create( + label=TEST_SMART_LINK_LABEL, + dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL + ) + + def test_smart_link_create_view(self): + response = self.client.post( + reverse('rest_api:smartlink-list'), { + 'label': TEST_SMART_LINK_LABEL + } + ) + + smart_link = SmartLink.objects.first() + self.assertEqual(response.data['id'], smart_link.pk) + self.assertEqual(response.data['label'], TEST_SMART_LINK_LABEL) + + self.assertEqual(SmartLink.objects.count(), 1) + self.assertEqual(smart_link.label, TEST_SMART_LINK_LABEL) + + def test_smart_link_delete_view(self): + smart_link = self._create_smart_link() + + self.client.delete( + reverse('rest_api:smartlink-detail', args=(smart_link.pk,)) + ) + + self.assertEqual(SmartLink.objects.count(), 0) + + def test_smart_link_detail_view(self): + smart_link = self._create_smart_link() + + response = self.client.get( + reverse('rest_api:smartlink-detail', args=(smart_link.pk,)) + ) + + self.assertEqual( + response.data['label'], TEST_SMART_LINK_LABEL + ) + + def test_smart_link_patch_view(self): + smart_link = self._create_smart_link() + + self.client.patch( + reverse('rest_api:smartlink-detail', args=(smart_link.pk,)), + data={ + 'label': TEST_SMART_LINK_LABEL_EDITED, + } + ) + + smart_link.refresh_from_db() + + self.assertEqual(smart_link.label, TEST_SMART_LINK_LABEL_EDITED) + + def test_smart_link_put_view(self): + smart_link = self._create_smart_link() + + self.client.put( + reverse('rest_api:smartlink-detail', args=(smart_link.pk,)), + data={ + 'label': TEST_SMART_LINK_LABEL_EDITED, + } + ) + + smart_link.refresh_from_db() + + self.assertEqual(smart_link.label, TEST_SMART_LINK_LABEL_EDITED) + + +@override_settings(OCR_AUTO_OCR=False) +class SmartLinkConditionAPITestCase(APITestCase): + def setUp(self): + 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 + ) + + def tearDown(self): + if hasattr(self, 'document_type'): + self.document_type.delete() + + def _create_document_type(self): + self.document_type = DocumentType.objects.create( + label=TEST_DOCUMENT_TYPE + ) + + def _create_document(self): + with open(TEST_SMALL_DOCUMENT_PATH) as file_object: + self.document = self.document_type.new_document( + file_object=file_object + ) + + def _create_smart_link(self): + self.smart_link = SmartLink.objects.create( + label=TEST_SMART_LINK_LABEL, + dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL + ) + self.smart_link.document_types.add(self.document_type) + + def _create_smart_link_condition(self): + self.smart_link_condition = SmartLinkCondition.objects.create( + smart_link=self.smart_link, + foreign_document_data=TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA, + expression=TEST_SMART_LINK_CONDITION_EXPRESSION, + operator=TEST_SMART_LINK_CONDITION_OPERATOR + ) + + def test_smart_link_condition_create_view(self): + self._create_document_type() + self._create_smart_link() + + response = self.client.post( + reverse( + 'rest_api:smartlinkcondition-list', args=(self.smart_link.pk,) + ), { + 'foreign_document_data': TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA, + 'expression': TEST_SMART_LINK_CONDITION_EXPRESSION, + 'operator': TEST_SMART_LINK_CONDITION_OPERATOR + } + ) + + smart_link_condition = SmartLinkCondition.objects.first() + self.assertEqual(response.data['id'], smart_link_condition.pk) + self.assertEqual( + response.data['operator'], TEST_SMART_LINK_CONDITION_OPERATOR + ) + + self.assertEqual(SmartLinkCondition.objects.count(), 1) + self.assertEqual( + smart_link_condition.operator, TEST_SMART_LINK_CONDITION_OPERATOR + ) + + def test_smart_link_condition_delete_view(self): + self._create_document_type() + self._create_smart_link() + self._create_smart_link_condition() + + self.client.delete( + reverse( + 'rest_api:smartlinkcondition-detail', + args=(self.smart_link.pk, self.smart_link_condition.pk) + ) + ) + + self.assertEqual(SmartLinkCondition.objects.count(), 0) + + def test_smart_link_condition_detail_view(self): + self._create_document_type() + self._create_smart_link() + self._create_smart_link_condition() + + response = self.client.get( + reverse( + 'rest_api:smartlinkcondition-detail', + args=(self.smart_link.pk, self.smart_link_condition.pk) + ) + ) + + self.assertEqual( + response.data['operator'], TEST_SMART_LINK_CONDITION_OPERATOR + ) + + def test_smart_link_condition_patch_view(self): + self._create_document_type() + self._create_smart_link() + self._create_smart_link_condition() + + self.client.patch( + reverse( + 'rest_api:smartlinkcondition-detail', + args=(self.smart_link.pk, self.smart_link_condition.pk) + ), + data={ + 'expression': TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED, + } + ) + + self.smart_link_condition.refresh_from_db() + + self.assertEqual( + self.smart_link_condition.expression, + TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED + ) + + def test_smart_link_condition_put_view(self): + self._create_document_type() + self._create_smart_link() + self._create_smart_link_condition() + + self.client.put( + reverse( + 'rest_api:smartlinkcondition-detail', + args=(self.smart_link.pk, self.smart_link_condition.pk) + ), + data={ + 'expression': TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED, + 'foreign_document_data': TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA, + 'operator': TEST_SMART_LINK_CONDITION_OPERATOR, + } + ) + + self.smart_link_condition.refresh_from_db() + + self.assertEqual( + self.smart_link_condition.expression, + TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED + ) diff --git a/mayan/apps/linking/tests/test_views.py b/mayan/apps/linking/tests/test_views.py index 317e2450b5..d57eed6620 100644 --- a/mayan/apps/linking/tests/test_views.py +++ b/mayan/apps/linking/tests/test_views.py @@ -13,7 +13,7 @@ from ..permissions import ( ) from .literals import ( - TEST_SMART_LINK_DYNAMIC_LABEL, TEST_SMART_LINK_EDITED_LABEL, + TEST_SMART_LINK_DYNAMIC_LABEL, TEST_SMART_LINK_LABEL_EDITED, TEST_SMART_LINK_LABEL ) @@ -83,7 +83,7 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase): response = self.post( 'linking:smart_link_edit', args=(smart_link.pk,), data={ - 'label': TEST_SMART_LINK_EDITED_LABEL + 'label': TEST_SMART_LINK_LABEL_EDITED } ) self.assertEqual(response.status_code, 403) @@ -101,13 +101,13 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase): response = self.post( 'linking:smart_link_edit', args=(smart_link.pk,), data={ - 'label': TEST_SMART_LINK_EDITED_LABEL + 'label': TEST_SMART_LINK_LABEL_EDITED }, follow=True ) smart_link = SmartLink.objects.get(pk=smart_link.pk) self.assertContains(response, text='update', status_code=200) - self.assertEqual(smart_link.label, TEST_SMART_LINK_EDITED_LABEL) + self.assertEqual(smart_link.label, TEST_SMART_LINK_LABEL_EDITED) def setup_smart_links(self): smart_link = SmartLink.objects.create( diff --git a/mayan/apps/linking/urls.py b/mayan/apps/linking/urls.py index c9a1c01a1a..3876fb73aa 100644 --- a/mayan/apps/linking/urls.py +++ b/mayan/apps/linking/urls.py @@ -2,6 +2,10 @@ from __future__ import unicode_literals from django.conf.urls import patterns, url +from .api_views import ( + APISmartLinkListView, APISmartLinkView, APISmartLinkConditionListView, + APISmartLinkConditionView +) from .views import ( DocumentSmartLinkListView, ResolvedSmartLinkView, SetupSmartLinkDocumentTypesView, SmartLinkConditionListView, @@ -61,3 +65,22 @@ urlpatterns = patterns( name='smart_link_condition_delete' ), ) + +api_urls = [ + url( + r'^smart_links/$', APISmartLinkListView.as_view(), name='smartlink-list' + ), + url( + r'^smart_links/(?P[0-9]+)/$', APISmartLinkView.as_view(), + name='smartlink-detail' + ), + url( + r'^smart_links/(?P[0-9]+)/conditions/$', + APISmartLinkConditionListView.as_view(), name='smartlinkcondition-list' + ), + url( + r'^smart_links/(?P[0-9]+)/conditions/(?P[0-9]+)/$', + APISmartLinkConditionView.as_view(), + name='smartlinkcondition-detail' + ), +]