diff --git a/mayan/apps/document_states/api_views.py b/mayan/apps/document_states/api_views.py index b7293b7c57..98dde08f32 100644 --- a/mayan/apps/document_states/api_views.py +++ b/mayan/apps/document_states/api_views.py @@ -9,6 +9,7 @@ from rest_framework import generics from mayan.apps.acls.models import AccessControlList from mayan.apps.documents.models import Document, DocumentType from mayan.apps.documents.permissions import permission_document_type_view +from mayan.apps.rest_api.mixins import ExternalObjectAPIViewSetMixin from mayan.apps.rest_api.viewsets import MayanAPIModelViewSet from .literals import WORKFLOW_IMAGE_TASK_TIMEOUT @@ -18,13 +19,7 @@ from .permissions import ( permission_workflow_edit, permission_workflow_view ) from .serializers import ( - WorkflowSerializer - - #NewWorkflowDocumentTypeSerializer, WorkflowDocumentTypeSerializer, - #WorkflowInstanceLogEntrySerializer, WorkflowInstanceSerializer, - #, WorkflowStateSerializer, WorkflowTransitionSerializer, - #WritableWorkflowInstanceLogEntrySerializer, WritableWorkflowSerializer, - #WritableWorkflowTransitionSerializer + WorkflowSerializer, WorkflowStateSerializer ) from .settings import settings_workflow_image_cache_time from .storages import storage_workflowimagecache @@ -46,6 +41,39 @@ class WorkflowAPIViewSet(MayanAPIModelViewSet): 'create': permission_workflow_create } + +class WorkflowStateAPIViewSet(ExternalObjectAPIViewSetMixin, MayanAPIModelViewSet): + external_object_class = Workflow + external_object_pk_url_kwarg = 'workflow_id' + lookup_url_kwarg = 'workflow_state_id' + serializer_class = WorkflowStateSerializer + + def get_external_object_permission(self): + action = getattr(self, 'action', None) + if action is None: + return None + elif action in ['create', 'destroy', 'partial_update', 'update']: + return permission_workflow_edit + else: + return permission_workflow_view + + def get_queryset(self): + return self.get_external_object().states.all() + + def get_serializer_context(self): + context = super(WorkflowStateAPIViewSet, self).get_serializer_context() + if self.kwargs: + context.update( + { + 'workflow': self.get_external_object(), + } + ) + + return context + + + + ''' class APIDocumentTypeWorkflowListView(generics.ListAPIView): """ diff --git a/mayan/apps/document_states/serializers.py b/mayan/apps/document_states/serializers.py index 10a526659f..b87649f3b3 100644 --- a/mayan/apps/document_states/serializers.py +++ b/mayan/apps/document_states/serializers.py @@ -9,6 +9,7 @@ from rest_framework.reverse import reverse from mayan.apps.documents.models import DocumentType from mayan.apps.documents.serializers import DocumentTypeSerializer +from mayan.apps.rest_api.relations import MultiKwargHyperlinkedIdentityField from mayan.apps.user_management.serializers import UserSerializer from .models import ( @@ -22,8 +23,11 @@ class WorkflowSerializer(serializers.HyperlinkedModelSerializer): # view_name='rest_api:workflow-document-type-list' #) #image_url = serializers.SerializerMethodField() - #states = WorkflowStateSerializer(many=True, required=False) #transitions = WorkflowTransitionSerializer(many=True, required=False) + state_list_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='workflow_id', + view_name='rest_api:workflow-state-list' + ) class Meta: extra_kwargs = { @@ -34,13 +38,40 @@ class WorkflowSerializer(serializers.HyperlinkedModelSerializer): } fields = ( #'document_types_url', 'image_url', - 'id', 'internal_name', 'label', 'url' - #'states', 'transitions', + 'id', 'internal_name', 'label', 'state_list_url', 'url' + #'transitions', ) model = Workflow -""" +class WorkflowStateSerializer(serializers.HyperlinkedModelSerializer): + workflow = WorkflowSerializer(read_only=True) + url = MultiKwargHyperlinkedIdentityField( + view_kwargs=( + { + 'lookup_field': 'workflow_id', + 'lookup_url_kwarg': 'workflow_id', + }, + { + 'lookup_field': 'pk', + 'lookup_url_kwarg': 'workflow_state_id', + } + ), + view_name='rest_api:workflow-state-detail' + ) + + class Meta: + fields = ( + 'completion', 'id', 'initial', 'label', 'url', 'workflow' + ) + model = WorkflowState + + def create(self, validated_data): + validated_data['workflow'] = self.context['workflow'] + return super(WorkflowStateSerializer, self).create(validated_data) + + +""" class NewWorkflowDocumentTypeSerializer(serializers.Serializer): document_type_pk = serializers.IntegerField( help_text=_('Primary key of the document type to be added.') @@ -78,33 +109,6 @@ class WorkflowDocumentTypeSerializer(DocumentTypeSerializer): ) -class WorkflowStateSerializer(serializers.HyperlinkedModelSerializer): - url = serializers.SerializerMethodField() - workflow_url = serializers.SerializerMethodField() - - class Meta: - fields = ( - 'completion', 'id', 'initial', 'label', 'url', 'workflow_url', - ) - model = WorkflowState - - def create(self, validated_data): - validated_data['workflow'] = self.context['workflow'] - return super(WorkflowStateSerializer, self).create(validated_data) - - def get_url(self, instance): - return reverse( - viewname='rest_api:workflowstate-detail', kwargs={ - 'workflow_pk': instance.workflow.pk, 'workflow_state_pk': instance.pk - }, request=self.context['request'], format=self.context['format'] - ) - - def get_workflow_url(self, instance): - return reverse( - viewname='rest_api:workflow-detail', kwargs={ - 'workflow_pk': instance.workflow.pk, - }, request=self.context['request'], format=self.context['format'] - ) class WorkflowTransitionSerializer(serializers.HyperlinkedModelSerializer): diff --git a/mayan/apps/document_states/tests/mixins.py b/mayan/apps/document_states/tests/mixins.py index 8ce2280cf4..9ca9e1a82a 100644 --- a/mayan/apps/document_states/tests/mixins.py +++ b/mayan/apps/document_states/tests/mixins.py @@ -11,30 +11,36 @@ from .literals import ( class WorkflowTestMixin(object): - def _create_workflow(self): - self.workflow = Workflow.objects.create( + def _create_test_workflow(self): + self.test_workflow = Workflow.objects.create( label=TEST_WORKFLOW_LABEL, internal_name=TEST_WORKFLOW_INTERNAL_NAME ) - def _create_workflow_states(self): - self.workflow_initial_state = WorkflowState.objects.create( + def _create_test_workflow_state(self): + self.test_workflow_state = WorkflowState.objects.create( + workflow=self.test_workflow, label=TEST_WORKFLOW_STATE_LABEL, + completion=TEST_WORKFLOW_STATE_COMPLETION + ) + + def _create_test_workflow_states(self): + self.test_workflow_initial_state = WorkflowState.objects.create( workflow=self.workflow, label=TEST_WORKFLOW_INITIAL_STATE_LABEL, completion=TEST_WORKFLOW_INITIAL_STATE_COMPLETION, initial=True ) - self.workflow_state = WorkflowState.objects.create( + self.test_workflow_state = WorkflowState.objects.create( workflow=self.workflow, label=TEST_WORKFLOW_STATE_LABEL, completion=TEST_WORKFLOW_STATE_COMPLETION ) - def _create_workflow_transition(self): - self.workflow_transition = WorkflowTransition.objects.create( + def _create_test_workflow_transition(self): + self.test_workflow_transition = WorkflowTransition.objects.create( workflow=self.workflow, label=TEST_WORKFLOW_TRANSITION_LABEL, origin_state=self.workflow_initial_state, destination_state=self.workflow_state ) - def _create_workflow_transitions(self): + def _create_test_workflow_transitions(self): self.workflow_transition = WorkflowTransition.objects.create( workflow=self.workflow, label=TEST_WORKFLOW_TRANSITION_LABEL, origin_state=self.workflow_initial_state, diff --git a/mayan/apps/document_states/tests/test_api.py b/mayan/apps/document_states/tests/test_api.py index 4cfc55b4ff..31fda12da3 100644 --- a/mayan/apps/document_states/tests/test_api.py +++ b/mayan/apps/document_states/tests/test_api.py @@ -26,14 +26,10 @@ from .literals import ( TEST_WORKFLOW_STATE_LABEL, TEST_WORKFLOW_STATE_LABEL_EDITED, TEST_WORKFLOW_TRANSITION_LABEL, TEST_WORKFLOW_TRANSITION_LABEL_EDITED ) +from .mixins import WorkflowTestMixin - - -class WorkflowAPIViewTestCase(BaseAPITestCase): - #def setUp(self): - # super(WorkflowAPIViewTestCase, self).setUp() - +class WorkflowAPIViewTestCase(WorkflowTestMixin, BaseAPITestCase): def _request_workflow_create_api_view(self): return self.post( viewname='rest_api:workflow-list', data={ @@ -59,12 +55,6 @@ class WorkflowAPIViewTestCase(BaseAPITestCase): self.assertEqual(Workflow.objects.count(), 1) - def _create_test_workflow(self): - self.test_workflow = Workflow.objects.create( - label=TEST_WORKFLOW_LABEL, - internal_name=TEST_WORKFLOW_INTERNAL_NAME - ) - def _request_workflow_delete_api_view(self): return self.delete( viewname='rest_api:workflow-detail', kwargs={ @@ -220,6 +210,196 @@ class WorkflowAPIViewTestCase(BaseAPITestCase): ) +class WorkflowStateAPIViewTestCase(WorkflowTestMixin, BaseAPITestCase): + def setUp(self): + super(WorkflowStateAPIViewTestCase, self).setUp() + self._create_test_workflow() + + def _request_workflow_state_create_api_view(self): + return self.post( + viewname='rest_api:workflow-state-list', + kwargs={'workflow_id': self.test_workflow.pk}, data={ + 'completion': TEST_WORKFLOW_STATE_COMPLETION, + 'label': TEST_WORKFLOW_STATE_LABEL + } + ) + + def test_workflow_state_create_api_view_no_access(self): + response = self._request_workflow_state_create_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertEqual(self.test_workflow.states.count(), 0) + + def test_workflow_state_create_api_view_with_permission(self): + self.grant_access( + obj=self.test_workflow, permission=permission_workflow_edit + ) + + response = self._request_workflow_state_create_api_view() + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual( + response.data['label'], TEST_WORKFLOW_STATE_LABEL + ) + + self.assertEqual(self.test_workflow.states.count(), 1) + + def _request_workflow_state_delete_api_view(self): + return self.delete( + viewname='rest_api:workflow-state-detail', kwargs={ + 'workflow_id': self.test_workflow.pk, + 'workflow_state_id': self.test_workflow_state.pk + } + ) + + def test_workflow_state_delete_api_view_no_access(self): + self._create_test_workflow_state() + workflow_state_count = self.test_workflow.states.count() + + response = self._request_workflow_state_delete_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertEqual( + workflow_state_count, self.test_workflow.states.count() + ) + + def test_workflow_state_delete_view_with_access(self): + self.expected_content_type = None + + self._create_test_workflow_state() + workflow_state_count = self.test_workflow.states.count() + self.grant_access(obj=self.test_workflow, permission=permission_workflow_edit) + + response = self._request_workflow_state_delete_api_view() + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + self.assertNotEqual( + workflow_state_count, self.test_workflow.states.count() + ) + + def _request_workflow_state_detail_api_view(self): + return self.get( + viewname='rest_api:workflow-state-detail', kwargs={ + 'workflow_id': self.test_workflow.pk, + 'workflow_state_id': self.test_workflow_state.pk + } + ) + + def test_workflow_state_detail_api_view_no_access(self): + self._create_test_workflow_state() + + response = self._request_workflow_state_detail_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertFalse('label' in response.json()) + + def test_workflow_state_detail_api_view_with_access(self): + self._create_test_workflow_state() + self.grant_access( + obj=self.test_workflow, permission=permission_workflow_view + ) + + response = self._request_workflow_state_detail_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.json()['label'], TEST_WORKFLOW_STATE_LABEL + ) + + def _request_workflow_state_edit_patch_api_view(self): + return self.patch( + viewname='rest_api:workflow-state-detail', kwargs={ + 'workflow_id': self.test_workflow.pk, + 'workflow_state_id': self.test_workflow_state.pk + }, data={'label': TEST_WORKFLOW_STATE_LABEL_EDITED} + ) + + def test_workflow_state_edit_patch_api_view_no_access(self): + self._create_test_workflow_state() + test_workflow_state = copy.copy(self.test_workflow_state) + + response = self._request_workflow_state_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_workflow_state.refresh_from_db() + self.assertEqual( + test_workflow_state.label, self.test_workflow_state.label + ) + + def test_workflow_state_edit_patch_api_view_with_access(self): + self._create_test_workflow_state() + test_workflow_state = copy.copy(self.test_workflow_state) + self.grant_access( + obj=self.test_workflow, permission=permission_workflow_edit + ) + + response = self._request_workflow_state_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_workflow_state.refresh_from_db() + self.assertNotEqual( + test_workflow_state.label, self.test_workflow_state.label + ) + + def _request_workflow_state_edit_put_api_view(self): + return self.put( + viewname='rest_api:workflow-state-detail', kwargs={ + 'workflow_id': self.test_workflow.pk, + 'workflow_state_id': self.test_workflow_state.pk + }, data={'label': TEST_WORKFLOW_STATE_LABEL_EDITED} + ) + + def test_workflow_state_edit_put_api_view_no_access(self): + self._create_test_workflow_state() + test_workflow_state = copy.copy(self.test_workflow_state) + + response = self._request_workflow_state_edit_put_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_workflow_state.refresh_from_db() + self.assertEqual( + test_workflow_state.label, self.test_workflow_state.label + ) + + def test_workflow_state_edit_put_api_view_with_access(self): + self._create_test_workflow_state() + test_workflow_state = copy.copy(self.test_workflow_state) + self.grant_access( + obj=self.test_workflow, permission=permission_workflow_edit + ) + + response = self._request_workflow_state_edit_put_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_workflow_state.refresh_from_db() + self.assertNotEqual( + test_workflow_state.label, self.test_workflow_state.label + ) + + def _request_workflow_state_list_api_view(self): + return self.get( + viewname='rest_api:workflow-state-list', kwargs={ + 'workflow_id': self.test_workflow.pk + } + ) + + def test_workflow_state_list_api_view_no_access(self): + self._create_test_workflow_state() + + response = self._request_workflow_state_list_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertFalse('label' in response.data) + + def test_workflow_state_list_api_view_with_access(self): + self._create_test_workflow_state() + self.grant_access( + obj=self.test_workflow, permission=permission_workflow_view + ) + + response = self._request_workflow_state_list_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['results'][0]['label'], self.test_workflow_state.label + ) + + """ @@ -415,179 +595,6 @@ class WorkflowAPIViewTestCase(BaseAPITestCase): self.assertEqual(response.data['results'][0]['label'], self.workflow.label) -class WorkflowStatesAPITestCase(BaseAPITestCase): - def setUp(self): - super(WorkflowStatesAPITestCase, self).setUp() - self.login_user() - - self.document_type = DocumentType.objects.create( - label=TEST_DOCUMENT_TYPE_LABEL - ) - - with open(TEST_SMALL_DOCUMENT_PATH, mode='rb') as file_object: - self.document = self.document_type.new_document( - file_object=file_object - ) - - def tearDown(self): - if hasattr(self, 'document_type'): - self.document_type.delete() - super(WorkflowStatesAPITestCase, self).tearDown() - - def _create_workflow(self): - self.workflow = Workflow.objects.create( - label=TEST_WORKFLOW_LABEL, - internal_name=TEST_WORKFLOW_INTERNAL_NAME - ) - - def _create_workflow_state(self): - self._create_workflow() - self.workflow_state = self.workflow.states.create( - completion=TEST_WORKFLOW_STATE_COMPLETION, - label=TEST_WORKFLOW_STATE_LABEL - ) - - def _request_workflow_state_create_view(self): - return self.post( - viewname='rest_api:workflowstate-list', - args=(self.workflow.pk,), data={ - 'completion': TEST_WORKFLOW_STATE_COMPLETION, - 'label': TEST_WORKFLOW_STATE_LABEL - } - ) - - def test_workflow_state_create_view_no_access(self): - self._create_workflow() - response = self._request_workflow_state_create_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.workflow.refresh_from_db() - self.assertEqual(self.workflow.states.count(), 0) - - def test_workflow_state_create_view_with_access(self): - self._create_workflow() - self.grant_access(permission=permission_workflow_edit, obj=self.workflow) - response = self._request_workflow_state_create_view() - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.workflow.refresh_from_db() - self.assertEqual( - self.workflow.states.first().label, TEST_WORKFLOW_STATE_LABEL - ) - - def _request_workflow_state_delete_view(self): - return self.delete( - viewname='rest_api:workflowstate-detail', - args=(self.workflow.pk, self.workflow_state.pk) - ) - - def test_workflow_state_delete_view_no_access(self): - self._create_workflow_state() - response = self._request_workflow_state_delete_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.workflow.refresh_from_db() - self.assertEqual(self.workflow.states.count(), 1) - - def test_workflow_state_delete_view_with_access(self): - self._create_workflow_state() - self.grant_access(permission=permission_workflow_edit, obj=self.workflow) - response = self._request_workflow_state_delete_view() - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.workflow.refresh_from_db() - self.assertEqual(self.workflow.states.count(), 0) - - def _request_workflow_state_detail_view(self): - return self.get( - viewname='rest_api:workflowstate-detail', - args=(self.workflow.pk, self.workflow_state.pk) - ) - - def test_workflow_state_detail_view_no_access(self): - self._create_workflow_state() - response = self._request_workflow_state_detail_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertFalse('label' in response.data) - - def test_workflow_state_detail_view_with_access(self): - self._create_workflow_state() - self.grant_access(permission=permission_workflow_view, obj=self.workflow) - response = self._request_workflow_state_detail_view() - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.data['label'], TEST_WORKFLOW_STATE_LABEL - ) - - def _request_workflow_state_list_view(self): - return self.get( - viewname='rest_api:workflowstate-list', args=(self.workflow.pk,), - ) - - def test_workflow_state_list_view_no_access(self): - self._create_workflow_state() - response = self._request_workflow_state_list_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertFalse('label' in response.data) - - def test_workflow_state_list_view_with_access(self): - self._create_workflow_state() - self.grant_access(permission=permission_workflow_view, obj=self.workflow) - response = self._request_workflow_state_list_view() - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.data['results'][0]['label'], TEST_WORKFLOW_STATE_LABEL - ) - - def _request_workflow_state_edit_view_via_patch(self): - return self.patch( - viewname='rest_api:workflowstate-detail', - args=(self.workflow.pk, self.workflow_state.pk), data={ - 'label': TEST_WORKFLOW_STATE_LABEL_EDITED - } - ) - - def test_workflow_state_edit_view_via_patch_no_access(self): - self._create_workflow_state() - response = self._request_workflow_state_edit_view_via_patch() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.workflow_state.refresh_from_db() - self.assertEqual( - self.workflow_state.label, TEST_WORKFLOW_STATE_LABEL - ) - - def test_workflow_state_edit_view_via_patch_with_access(self): - self._create_workflow_state() - self.grant_access(permission=permission_workflow_edit, obj=self.workflow) - response = self._request_workflow_state_edit_view_via_patch() - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.workflow_state.refresh_from_db() - self.assertEqual( - self.workflow_state.label, TEST_WORKFLOW_STATE_LABEL_EDITED - ) - - def _request_workflow_state_edit_view_via_put(self): - return self.put( - viewname='rest_api:workflowstate-detail', - args=(self.workflow.pk, self.workflow_state.pk), data={ - 'label': TEST_WORKFLOW_STATE_LABEL_EDITED - } - ) - - def test_workflow_state_edit_view_via_put_no_access(self): - self._create_workflow_state() - response = self._request_workflow_state_edit_view_via_put() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.workflow_state.refresh_from_db() - self.assertEqual( - self.workflow_state.label, TEST_WORKFLOW_STATE_LABEL - ) - - def test_workflow_state_edit_view_via_put_with_access(self): - self._create_workflow_state() - self.grant_access(permission=permission_workflow_edit, obj=self.workflow) - response = self._request_workflow_state_edit_view_via_put() - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.workflow_state.refresh_from_db() - self.assertEqual( - self.workflow_state.label, TEST_WORKFLOW_STATE_LABEL_EDITED - ) class WorkflowTransitionsAPITestCase(BaseAPITestCase): diff --git a/mayan/apps/document_states/urls.py b/mayan/apps/document_states/urls.py index 026efd8ee9..7e5f6dc442 100644 --- a/mayan/apps/document_states/urls.py +++ b/mayan/apps/document_states/urls.py @@ -2,7 +2,10 @@ from __future__ import unicode_literals from django.conf.urls import url -from .api_views import WorkflowAPIViewSet +from .api_views import ( + WorkflowAPIViewSet, + WorkflowStateAPIViewSet +) from .views import ( DocumentWorkflowInstanceListView, ToolLaunchAllWorkflows, WorkflowCreateView, WorkflowDeleteView, WorkflowDocumentTypesView, @@ -179,15 +182,15 @@ api_router_entries = ( 'prefix': r'workflows', 'viewset': WorkflowAPIViewSet, 'basename': 'workflow' }, + { + 'prefix': r'workflows/(?P[^/.]+)/states', + 'viewset': WorkflowStateAPIViewSet, 'basename': 'workflow-state' + } #{ # 'prefix': r'metadata_types/(?P[^/.]+)/document_type_relations', # 'viewset': MetadataTypeDocumentTypeRelationAPIViewSet, # 'basename': 'metadata_type-document_type_relation' #}, - #{ - # 'prefix': r'documents/(?P[^/.]+)/metadata', - # 'viewset': DocumentMetadataAPIViewSet, 'basename': 'document-metadata' - #} ) '''