Add workflow transition API endpoints and tests.

This commit is contained in:
Roberto Rosario
2017-02-09 05:16:06 -04:00
parent ab68723cf6
commit 0ff0841826
5 changed files with 462 additions and 33 deletions

View File

@@ -7,19 +7,19 @@ from rest_framework import generics
from acls.models import AccessControlList
from documents.permissions import permission_document_type_view
from documents.serializers import DocumentSerializer, DocumentTypeSerializer
from permissions import Permission
from rest_api.filters import MayanObjectPermissionsFilter
from rest_api.permissions import MayanPermission
from .models import Workflow, WorkflowState
from .models import Workflow
from .permissions import (
permission_workflow_create, permission_workflow_delete,
permission_workflow_edit, permission_workflow_view
)
from .serializers import (
NewWorkflowDocumentTypeSerializer, WorkflowDocumentTypeSerializer,
WorkflowSerializer, WorkflowStateSerializer, WritableWorkflowSerializer
WorkflowSerializer, WorkflowStateSerializer, WorkflowTransitionSerializer,
WritableWorkflowSerializer, WritableWorkflowTransitionSerializer
)
@@ -245,7 +245,7 @@ class APIWorkflowView(generics.RetrieveUpdateDestroyAPIView):
return super(APIWorkflowView, self).put(*args, **kwargs)
## Workflow state views
# Workflow state views
class APIWorkflowStateListView(generics.ListCreateAPIView):
@@ -299,6 +299,7 @@ class APIWorkflowStateListView(generics.ListCreateAPIView):
class APIWorkflowStateView(generics.RetrieveUpdateDestroyAPIView):
lookup_url_kwarg = 'state_pk'
serializer_class = WorkflowStateSerializer
def delete(self, *args, **kwargs):
"""
@@ -317,12 +318,6 @@ class APIWorkflowStateView(generics.RetrieveUpdateDestroyAPIView):
def get_queryset(self):
return self.get_workflow().states.all()
def get_serializer_class(self):
if self.request.method == 'GET':
return WorkflowStateSerializer
else:
return WorkflowStateSerializer # TODO: Writable
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
@@ -368,3 +363,128 @@ class APIWorkflowStateView(generics.RetrieveUpdateDestroyAPIView):
return super(APIWorkflowStateView, self).put(*args, **kwargs)
# Workflow transition views
class APIWorkflowTransitionListView(generics.ListCreateAPIView):
def get(self, *args, **kwargs):
"""
Returns a list of all the workflow transitions.
"""
return super(APIWorkflowTransitionListView, self).get(*args, **kwargs)
def get_queryset(self):
return self.get_workflow().transitions.all()
def get_serializer_class(self):
if self.request.method == 'GET':
return WorkflowTransitionSerializer
else:
return WritableWorkflowTransitionSerializer
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'format': self.format_kwarg,
'request': self.request,
'workflow': self.get_workflow(),
'view': self
}
def get_workflow(self):
if self.request.method == 'GET':
permission_required = permission_workflow_view
else:
permission_required = permission_workflow_edit
workflow = get_object_or_404(Workflow, pk=self.kwargs['pk'])
try:
Permission.check_permissions(
self.request.user, (permission_required,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_required, self.request.user, workflow
)
return workflow
def post(self, *args, **kwargs):
"""
Create a new workflow transition.
"""
return super(APIWorkflowTransitionListView, self).post(*args, **kwargs)
class APIWorkflowTransitionView(generics.RetrieveUpdateDestroyAPIView):
lookup_url_kwarg = 'transition_pk'
def delete(self, *args, **kwargs):
"""
Delete the selected workflow transition.
"""
return super(APIWorkflowTransitionView, self).delete(*args, **kwargs)
def get(self, *args, **kwargs):
"""
Return the details of the selected workflow transition.
"""
return super(APIWorkflowTransitionView, self).get(*args, **kwargs)
def get_queryset(self):
return self.get_workflow().transitions.all()
def get_serializer_class(self):
if self.request.method == 'GET':
return WorkflowTransitionSerializer
else:
return WritableWorkflowTransitionSerializer
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'format': self.format_kwarg,
'request': self.request,
'workflow': self.get_workflow(),
'view': self
}
def get_workflow(self):
if self.request.method == 'GET':
permission_required = permission_workflow_view
else:
permission_required = permission_workflow_edit
workflow = get_object_or_404(Workflow, pk=self.kwargs['pk'])
try:
Permission.check_permissions(
self.request.user, (permission_required,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_required, self.request.user, workflow
)
return workflow
def patch(self, *args, **kwargs):
"""
Edit the selected workflow transition.
"""
return super(APIWorkflowTransitionView, self).patch(*args, **kwargs)
def put(self, *args, **kwargs):
"""
Edit the selected workflow transition.
"""
return super(APIWorkflowTransitionView, self).put(*args, **kwargs)

View File

@@ -8,7 +8,7 @@ from rest_framework.reverse import reverse
from documents.models import DocumentType
from documents.serializers import DocumentTypeSerializer
from .models import Workflow, WorkflowState
from .models import Workflow, WorkflowState, WorkflowTransition
class NewWorkflowDocumentTypeSerializer(serializers.Serializer):
@@ -49,8 +49,8 @@ class WorkflowDocumentTypeSerializer(DocumentTypeSerializer):
class WorkflowStateSerializer(serializers.HyperlinkedModelSerializer):
workflow_url = serializers.SerializerMethodField()
url = serializers.SerializerMethodField()
workflow_url = serializers.SerializerMethodField()
class Meta:
fields = (
@@ -77,18 +77,107 @@ class WorkflowStateSerializer(serializers.HyperlinkedModelSerializer):
)
class WorkflowTransitionSerializer(serializers.HyperlinkedModelSerializer):
destination_state = WorkflowStateSerializer()
origin_state = WorkflowStateSerializer()
url = serializers.SerializerMethodField()
workflow_url = serializers.SerializerMethodField()
class Meta:
fields = (
'destination_state', 'id', 'label', 'origin_state', 'url',
'workflow_url',
)
model = WorkflowTransition
def get_url(self, instance):
return reverse(
'rest_api:workflowtransition-detail', args=(
instance.workflow.pk, instance.pk
), request=self.context['request'], format=self.context['format']
)
def get_workflow_url(self, instance):
return reverse(
'rest_api:workflow-detail', args=(
instance.workflow.pk,
), request=self.context['request'], format=self.context['format']
)
class WritableWorkflowTransitionSerializer(serializers.ModelSerializer):
destination_state_pk = serializers.IntegerField(
help_text=_('Primary key of the destination state to be added.'),
write_only=True
)
origin_state_pk = serializers.IntegerField(
help_text=_('Primary key of the origin state to be added.'),
write_only=True
)
url = serializers.SerializerMethodField()
workflow_url = serializers.SerializerMethodField()
class Meta:
fields = (
'destination_state_pk', 'id', 'label', 'origin_state_pk', 'url',
'workflow_url',
)
model = WorkflowTransition
def create(self, validated_data):
validated_data['destination_state'] = WorkflowState.objects.get(
pk=validated_data.pop('destination_state_pk')
)
validated_data['origin_state'] = WorkflowState.objects.get(
pk=validated_data.pop('origin_state_pk')
)
validated_data['workflow'] = self.context['workflow']
return super(WritableWorkflowTransitionSerializer, self).create(
validated_data
)
def get_url(self, instance):
return reverse(
'rest_api:workflowtransition-detail', args=(
instance.workflow.pk, instance.pk
), request=self.context['request'], format=self.context['format']
)
def get_workflow_url(self, instance):
return reverse(
'rest_api:workflow-detail', args=(
instance.workflow.pk,
), request=self.context['request'], format=self.context['format']
)
def update(self, instance, validated_data):
validated_data['destination_state'] = WorkflowState.objects.get(
pk=validated_data.pop('destination_state_pk')
)
validated_data['origin_state'] = WorkflowState.objects.get(
pk=validated_data.pop('origin_state_pk')
)
return super(WritableWorkflowTransitionSerializer, self).update(
instance, validated_data
)
class WorkflowSerializer(serializers.HyperlinkedModelSerializer):
document_types_url = serializers.HyperlinkedIdentityField(
view_name='rest_api:workflow-document-type-list'
)
states = WorkflowStateSerializer(many=True, required=False)
transitions = WorkflowTransitionSerializer(many=True, required=False)
class Meta:
extra_kwargs = {
'url': {'view_name': 'rest_api:workflow-detail'},
}
fields = (
'document_types_url', 'id', 'label', 'states', 'url'
'document_types_url', 'id', 'label', 'states', 'transitions',
'url'
)
model = Workflow

View File

@@ -4,6 +4,8 @@ TEST_WORKFLOW_LABEL = 'test workflow label'
TEST_WORKFLOW_LABEL_EDITED = 'test workflow label edited'
TEST_WORKFLOW_INITIAL_STATE_LABEL = 'test initial state'
TEST_WORKFLOW_INITIAL_STATE_COMPLETION = 33
TEST_WORKFLOW_STATE_LABEL = 'test state'
TEST_WORKFLOW_STATE_LABEL = 'test state label'
TEST_WORKFLOW_STATE_LABEL_EDITED = 'test state label edited'
TEST_WORKFLOW_STATE_COMPLETION = 66
TEST_WORKFLOW_TRANSITION_LABEL = 'test transtition'
TEST_WORKFLOW_TRANSITION_LABEL = 'test transtition label'
TEST_WORKFLOW_TRANSITION_LABEL_EDITED = 'test transtition label edited'

View File

@@ -8,7 +8,6 @@ from django.utils.encoding import force_text
from rest_framework.test import APITestCase
from documents.models import DocumentType
from documents.permissions import permission_document_type_view
from documents.tests.literals import (
TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
)
@@ -20,7 +19,10 @@ from ..models import Workflow
from .literals import (
TEST_WORKFLOW_LABEL, TEST_WORKFLOW_LABEL_EDITED,
TEST_WORKFLOW_STATE_COMPLETION, TEST_WORKFLOW_STATE_LABEL
TEST_WORKFLOW_INITIAL_STATE_COMPLETION, TEST_WORKFLOW_INITIAL_STATE_LABEL,
TEST_WORKFLOW_STATE_COMPLETION, TEST_WORKFLOW_STATE_LABEL,
TEST_WORKFLOW_STATE_LABEL_EDITED, TEST_WORKFLOW_TRANSITION_LABEL,
TEST_WORKFLOW_TRANSITION_LABEL_EDITED
)
@@ -99,7 +101,7 @@ class WorkflowAPITestCase(APITestCase):
def test_workflow_document_type_create_view(self):
workflow = self._create_workflow()
response = self.client.post(
self.client.post(
reverse(
'rest_api:workflow-document-type-list',
args=(workflow.pk,)
@@ -114,7 +116,7 @@ class WorkflowAPITestCase(APITestCase):
workflow = self._create_workflow()
workflow.document_types.add(self.document_type)
response = self.client.delete(
self.client.delete(
reverse(
'rest_api:workflow-document-type-detail',
args=(workflow.pk, self.document_type.pk)
@@ -147,8 +149,9 @@ class WorkflowAPITestCase(APITestCase):
workflow.document_types.add(self.document_type)
response = self.client.get(
reverse('rest_api:workflow-document-type-list',
args=(workflow.pk,))
reverse(
'rest_api:workflow-document-type-list', args=(workflow.pk,)
)
)
self.assertEqual(
@@ -165,7 +168,7 @@ class WorkflowAPITestCase(APITestCase):
def test_workflow_put_view(self):
workflow = self._create_workflow()
response = self.client.put(
self.client.put(
reverse('rest_api:workflow-detail', args=(workflow.pk,)),
data={'label': TEST_WORKFLOW_LABEL_EDITED}
)
@@ -176,7 +179,7 @@ class WorkflowAPITestCase(APITestCase):
def test_workflow_patch_view(self):
workflow = self._create_workflow()
response = self.client.patch(
self.client.patch(
reverse('rest_api:workflow-detail', args=(workflow.pk,)),
data={'label': TEST_WORKFLOW_LABEL_EDITED}
)
@@ -223,7 +226,7 @@ class WorkflowStatesAPITestCase(APITestCase):
def test_workflow_state_create_view(self):
self._create_workflow()
response = self.client.post(
self.client.post(
reverse(
'rest_api:workflowstate-list', args=(self.workflow.pk,)
), data={
@@ -241,7 +244,7 @@ class WorkflowStatesAPITestCase(APITestCase):
def test_workflow_state_delete_view(self):
self._create_workflow_state()
response = self.client.delete(
self.client.delete(
reverse(
'rest_api:workflowstate-detail',
args=(self.workflow.pk, self.workflow_state.pk)
@@ -276,3 +279,209 @@ class WorkflowStatesAPITestCase(APITestCase):
self.assertEqual(
response.data['results'][0]['label'], TEST_WORKFLOW_STATE_LABEL
)
def test_workflow_state_patch_view(self):
self._create_workflow_state()
self.client.patch(
reverse(
'rest_api:workflowstate-detail',
args=(self.workflow.pk, self.workflow_state.pk)
),
data={'label': TEST_WORKFLOW_STATE_LABEL_EDITED}
)
self.workflow_state.refresh_from_db()
self.assertEqual(
self.workflow_state.label,
TEST_WORKFLOW_STATE_LABEL_EDITED
)
def test_workflow_state_put_view(self):
self._create_workflow_state()
self.client.put(
reverse(
'rest_api:workflowstate-detail',
args=(self.workflow.pk, self.workflow_state.pk)
),
data={'label': TEST_WORKFLOW_STATE_LABEL_EDITED}
)
self.workflow_state.refresh_from_db()
self.assertEqual(
self.workflow_state.label,
TEST_WORKFLOW_STATE_LABEL_EDITED
)
@override_settings(OCR_AUTO_OCR=False)
class WorkflowTransitionsAPITestCase(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
)
self.document_type = DocumentType.objects.create(
label=TEST_DOCUMENT_TYPE
)
with open(TEST_SMALL_DOCUMENT_PATH) 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()
def _create_workflow(self):
self.workflow = Workflow.objects.create(label=TEST_WORKFLOW_LABEL)
def _create_workflow_states(self):
self._create_workflow()
self.workflow_state_1 = self.workflow.states.create(
completion=TEST_WORKFLOW_INITIAL_STATE_COMPLETION,
label=TEST_WORKFLOW_INITIAL_STATE_LABEL
)
self.workflow_state_2 = self.workflow.states.create(
completion=TEST_WORKFLOW_STATE_COMPLETION,
label=TEST_WORKFLOW_STATE_LABEL
)
def _create_workflow_transition(self):
self._create_workflow_states()
self.workflow_transition = self.workflow.transitions.create(
label=TEST_WORKFLOW_TRANSITION_LABEL,
origin_state=self.workflow_state_1,
destination_state=self.workflow_state_2,
)
def test_workflow_transition_create_view(self):
self._create_workflow_states()
self.client.post(
reverse(
'rest_api:workflowtransition-list', args=(self.workflow.pk,)
), data={
'label': TEST_WORKFLOW_TRANSITION_LABEL,
'origin_state_pk': self.workflow_state_1.pk,
'destination_state_pk': self.workflow_state_2.pk,
}
)
self.workflow.refresh_from_db()
self.assertEqual(
self.workflow.transitions.first().label,
TEST_WORKFLOW_TRANSITION_LABEL
)
def test_workflow_transition_delete_view(self):
self._create_workflow_transition()
self.client.delete(
reverse(
'rest_api:workflowtransition-detail',
args=(self.workflow.pk, self.workflow_transition.pk)
),
)
self.workflow.refresh_from_db()
self.assertEqual(self.workflow.transitions.count(), 0)
def test_workflow_transition_detail_view(self):
self._create_workflow_transition()
response = self.client.get(
reverse(
'rest_api:workflowtransition-detail',
args=(self.workflow.pk, self.workflow_transition.pk)
),
)
self.assertEqual(
response.data['label'], TEST_WORKFLOW_TRANSITION_LABEL
)
def test_workflow_transition_list_view(self):
self._create_workflow_transition()
response = self.client.get(
reverse(
'rest_api:workflowtransition-list', args=(self.workflow.pk,)
),
)
self.assertEqual(
response.data['results'][0]['label'],
TEST_WORKFLOW_TRANSITION_LABEL
)
def test_workflow_transition_patch_view(self):
self._create_workflow_transition()
self.client.patch(
reverse(
'rest_api:workflowtransition-detail',
args=(self.workflow.pk, self.workflow_transition.pk)
),
data={
'label': TEST_WORKFLOW_TRANSITION_LABEL_EDITED,
'origin_state_pk': self.workflow_state_2.pk,
'destination_state_pk': self.workflow_state_1.pk,
}
)
self.workflow_transition.refresh_from_db()
self.assertEqual(
self.workflow_transition.label,
TEST_WORKFLOW_TRANSITION_LABEL_EDITED
)
self.assertEqual(
self.workflow_transition.origin_state,
self.workflow_state_2
)
self.assertEqual(
self.workflow_transition.destination_state,
self.workflow_state_1
)
def test_workflow_transition_put_view(self):
self._create_workflow_transition()
self.client.put(
reverse(
'rest_api:workflowtransition-detail',
args=(self.workflow.pk, self.workflow_transition.pk)
),
data={
'label': TEST_WORKFLOW_TRANSITION_LABEL_EDITED,
'origin_state_pk': self.workflow_state_2.pk,
'destination_state_pk': self.workflow_state_1.pk,
}
)
self.workflow_transition.refresh_from_db()
self.assertEqual(
self.workflow_transition.label,
TEST_WORKFLOW_TRANSITION_LABEL_EDITED
)
self.assertEqual(
self.workflow_transition.origin_state,
self.workflow_state_2
)
self.assertEqual(
self.workflow_transition.destination_state,
self.workflow_state_1
)

View File

@@ -5,6 +5,7 @@ from django.conf.urls import patterns, url
from .api_views import (
APIWorkflowDocumentTypeList, APIWorkflowDocumentTypeView,
APIWorkflowListView, APIWorkflowStateListView, APIWorkflowStateView,
APIWorkflowTransitionListView, APIWorkflowTransitionView,
APIWorkflowView
)
from .views import (
@@ -104,14 +105,6 @@ urlpatterns = patterns(
)
api_urls = [
url(
r'^workflows/(?P<pk>[0-9]+)/states/$',
APIWorkflowStateListView.as_view(), name='workflowstate-list'
),
url(
r'^workflows/(?P<pk>[0-9]+)/states/(?P<state_pk>[0-9]+)/$',
APIWorkflowStateView.as_view(), name='workflowstate-detail'
),
url(r'^workflows/$', APIWorkflowListView.as_view(), name='workflow-list'),
url(
r'^workflows/(?P<pk>[0-9]+)/$', APIWorkflowView.as_view(),
@@ -127,4 +120,20 @@ api_urls = [
APIWorkflowDocumentTypeView.as_view(),
name='workflow-document-type-detail'
),
url(
r'^workflows/(?P<pk>[0-9]+)/states/$',
APIWorkflowStateListView.as_view(), name='workflowstate-list'
),
url(
r'^workflows/(?P<pk>[0-9]+)/states/(?P<state_pk>[0-9]+)/$',
APIWorkflowStateView.as_view(), name='workflowstate-detail'
),
url(
r'^workflows/(?P<pk>[0-9]+)/transitions/$',
APIWorkflowTransitionListView.as_view(), name='workflowtransition-list'
),
url(
r'^workflows/(?P<pk>[0-9]+)/transitions/(?P<transition_pk>[0-9]+)/$',
APIWorkflowTransitionView.as_view(), name='workflowtransition-detail'
),
]