Inital work on the document states API
This commit is contained in:
254
mayan/apps/document_states/api_views.py
Normal file
254
mayan/apps/document_states/api_views.py
Normal file
@@ -0,0 +1,254 @@
|
||||
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 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
|
||||
from .permissions import (
|
||||
permission_workflow_create, permission_workflow_delete,
|
||||
permission_workflow_edit, permission_workflow_view
|
||||
)
|
||||
from .serializers import (
|
||||
NewWorkflowDocumentTypeSerializer, WorkflowDocumentTypeSerializer,
|
||||
WorkflowSerializer, WritableWorkflowSerializer
|
||||
)
|
||||
|
||||
|
||||
class APIWorkflowDocumentTypeList(generics.ListCreateAPIView):
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {
|
||||
'GET': (permission_document_type_view,),
|
||||
}
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
Returns a list of all the document types attached to a workflow.
|
||||
"""
|
||||
|
||||
return super(APIWorkflowDocumentTypeList, self).get(*args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
This view returns a list of document types that belong to a workflow
|
||||
RESEARCH: Could the documents.api_views.APIDocumentTypeList class
|
||||
be subclasses for this?
|
||||
"""
|
||||
|
||||
return self.get_workflow().document_types.all()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.method == 'GET':
|
||||
return WorkflowDocumentTypeSerializer
|
||||
elif self.request.method == 'POST':
|
||||
return NewWorkflowDocumentTypeSerializer
|
||||
|
||||
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):
|
||||
"""
|
||||
Retrieve the parent workflow of the workflow document type.
|
||||
Perform custom permission and access check.
|
||||
"""
|
||||
|
||||
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 perform_create(self, serializer):
|
||||
# """
|
||||
# RESEARCH: This is not needed if the serializer uses the context
|
||||
# dictionary instead. However is that an acceptable "proper" way
|
||||
# to do it?
|
||||
# """
|
||||
#
|
||||
# serializer.save(workflow=self.get_workflow())
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
Attach a document type to a specified workflow.
|
||||
"""
|
||||
|
||||
return super(
|
||||
APIWorkflowDocumentTypeList, self
|
||||
).post(request, *args, **kwargs)
|
||||
|
||||
|
||||
class APIWorkflowDocumentTypeView(generics.RetrieveDestroyAPIView):
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
lookup_url_kwarg = 'document_pk'
|
||||
mayan_object_permissions = {
|
||||
'GET': (permission_document_type_view,),
|
||||
}
|
||||
serializer_class = WorkflowDocumentTypeSerializer
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
"""
|
||||
Remove a document type from the selected workflow.
|
||||
"""
|
||||
|
||||
return super(
|
||||
APIWorkflowDocumentTypeView, self
|
||||
).delete(request, *args, **kwargs)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
Returns the details of the selected workflow document type.
|
||||
"""
|
||||
|
||||
return super(APIWorkflowDocumentTypeView, self).get(*args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_workflow().document_types.all()
|
||||
|
||||
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):
|
||||
"""
|
||||
This view returns a document types that belongs to a workflow
|
||||
RESEARCH: Could the documents.api_views.APIDocumentTypeView class
|
||||
be subclasses for this?
|
||||
RESEARCH: Since this is a parent-child API view could this be made
|
||||
into a generic API class?
|
||||
RESEARCH: Reuse get_workflow method from APIWorkflowDocumentTypeList?
|
||||
"""
|
||||
|
||||
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 perform_destroy(self, instance):
|
||||
"""
|
||||
RESEARCH: Move this kind of methods to the serializer instead it that
|
||||
ability becomes available in Django REST framework
|
||||
"""
|
||||
print "DESTROY!"
|
||||
self.get_workflow().documents.remove(instance)
|
||||
|
||||
|
||||
class APIWorkflowListView(generics.ListCreateAPIView):
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {
|
||||
'GET': (permission_workflow_view,),
|
||||
'POST': (permission_workflow_create,)
|
||||
}
|
||||
permission_classes = (MayanPermission,)
|
||||
queryset = Workflow.objects.all()
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
Returns a list of all the workflows.
|
||||
"""
|
||||
return super(APIWorkflowListView, self).get(*args, **kwargs)
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.method == 'GET':
|
||||
return WorkflowSerializer
|
||||
else:
|
||||
return WritableWorkflowSerializer
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
"""
|
||||
Create a new workflow.
|
||||
"""
|
||||
return super(APIWorkflowListView, self).post(*args, **kwargs)
|
||||
|
||||
|
||||
class APIWorkflowView(generics.RetrieveUpdateDestroyAPIView):
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {
|
||||
'DELETE': (permission_workflow_delete,),
|
||||
'GET': (permission_workflow_view,),
|
||||
'PATCH': (permission_workflow_edit,),
|
||||
'PUT': (permission_workflow_edit,)
|
||||
}
|
||||
queryset = Workflow.objects.all()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Delete the selected workflow.
|
||||
"""
|
||||
|
||||
return super(APIWorkflowView, self).delete(*args, **kwargs)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
Return the details of the selected workflow.
|
||||
"""
|
||||
|
||||
return super(APIWorkflowView, self).get(*args, **kwargs)
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.method == 'GET':
|
||||
return WorkflowSerializer
|
||||
else:
|
||||
return WritableWorkflowSerializer
|
||||
|
||||
def patch(self, *args, **kwargs):
|
||||
"""
|
||||
Edit the selected workflow.
|
||||
"""
|
||||
|
||||
return super(APIWorkflowView, self).patch(*args, **kwargs)
|
||||
|
||||
def put(self, *args, **kwargs):
|
||||
"""
|
||||
Edit the selected workflow.
|
||||
"""
|
||||
|
||||
return super(APIWorkflowView, self).put(*args, **kwargs)
|
||||
@@ -10,6 +10,7 @@ from common import (
|
||||
)
|
||||
from common.widgets import two_state_template
|
||||
from navigation import SourceColumn
|
||||
from rest_api.classes import APIEndPoint
|
||||
|
||||
from .handlers import launch_workflow
|
||||
from .links import (
|
||||
@@ -33,6 +34,8 @@ class DocumentStatesApp(MayanAppConfig):
|
||||
def ready(self):
|
||||
super(DocumentStatesApp, self).ready()
|
||||
|
||||
APIEndPoint(app=self, version_string='1')
|
||||
|
||||
Document = apps.get_model(
|
||||
app_label='documents', model_name='Document'
|
||||
)
|
||||
|
||||
123
mayan/apps/document_states/serializers.py
Normal file
123
mayan/apps/document_states/serializers.py
Normal file
@@ -0,0 +1,123 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from documents.models import DocumentType
|
||||
from documents.serializers import DocumentTypeSerializer
|
||||
|
||||
from .models import Workflow
|
||||
|
||||
|
||||
class NewWorkflowDocumentTypeSerializer(serializers.Serializer):
|
||||
document_type_pk = serializers.IntegerField(
|
||||
help_text=_('Primary key of the document type to be added.')
|
||||
)
|
||||
|
||||
def create(self, validated_data):
|
||||
document_type = DocumentType.objects.get(
|
||||
pk=validated_data['document_type_pk']
|
||||
)
|
||||
self.context['workflow'].document_types.add(document_type)
|
||||
|
||||
return validated_data
|
||||
|
||||
|
||||
class WorkflowDocumentTypeSerializer(DocumentTypeSerializer):
|
||||
workflow_document_type_url = serializers.SerializerMethodField(
|
||||
help_text=_(
|
||||
'API URL pointing to a document type in relation to the '
|
||||
'workflow to which it is attached. This URL is different than '
|
||||
'the canonical document type URL.'
|
||||
)
|
||||
)
|
||||
|
||||
class Meta(DocumentTypeSerializer.Meta):
|
||||
fields = DocumentTypeSerializer.Meta.fields + (
|
||||
'workflow_document_type_url',
|
||||
)
|
||||
read_only_fields = DocumentTypeSerializer.Meta.fields
|
||||
|
||||
def get_workflow_document_type_url(self, instance):
|
||||
return reverse(
|
||||
'rest_api:workflow-document-type-detail', args=(
|
||||
self.context['workflow'].pk, instance.pk
|
||||
), request=self.context['request'], format=self.context['format']
|
||||
)
|
||||
|
||||
|
||||
class WorkflowSerializer(serializers.HyperlinkedModelSerializer):
|
||||
document_types_url = serializers.HyperlinkedIdentityField(
|
||||
view_name='rest_api:workflow-document-type-list'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
extra_kwargs = {
|
||||
'url': {'view_name': 'rest_api:workflow-detail'},
|
||||
}
|
||||
fields = (
|
||||
'document_types_url', 'get_initial_state', 'id', 'label', 'url'
|
||||
)
|
||||
model = Workflow
|
||||
|
||||
|
||||
class WritableWorkflowSerializer(serializers.ModelSerializer):
|
||||
document_types_pk_list = serializers.CharField(
|
||||
help_text=_(
|
||||
'Comma separated list of document type primary keys to which this '
|
||||
'workflow will be attached.'
|
||||
), required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
extra_kwargs = {
|
||||
'url': {'view_name': 'rest_api:workflow-detail'},
|
||||
}
|
||||
fields = (
|
||||
'document_types_pk_list', 'label', 'id', 'url',
|
||||
)
|
||||
model = Workflow
|
||||
|
||||
def _add_document_types(self, document_types_pk_list, instance):
|
||||
instance.document_types.add(
|
||||
*DocumentType.objects.filter(
|
||||
pk__in=document_types_pk_list.split(',')
|
||||
)
|
||||
)
|
||||
|
||||
def create(self, validated_data):
|
||||
document_types_pk_list = validated_data.pop(
|
||||
'document_types_pk_list', ''
|
||||
)
|
||||
|
||||
instance = super(WritableWorkflowSerializer, self).create(
|
||||
validated_data
|
||||
)
|
||||
|
||||
if document_types_pk_list:
|
||||
self._add_document_types(
|
||||
document_types_pk_list=document_types_pk_list,
|
||||
instance=instance
|
||||
)
|
||||
|
||||
return instance
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
document_types_pk_list = validated_data.pop(
|
||||
'document_types_pk_list', ''
|
||||
)
|
||||
|
||||
instance = super(WritableWorkflowSerializer, self).update(
|
||||
instance, validated_data
|
||||
)
|
||||
|
||||
if document_types_pk_list:
|
||||
instance.documents.clear()
|
||||
self._add_documents(
|
||||
document_types_pk_list=document_types_pk_list,
|
||||
instance=instance
|
||||
)
|
||||
|
||||
return instance
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
TEST_WORKFLOW_LABEL = 'test workflow'
|
||||
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'
|
||||
|
||||
123
mayan/apps/document_states/tests/test_api.py
Normal file
123
mayan/apps/document_states/tests/test_api.py
Normal file
@@ -0,0 +1,123 @@
|
||||
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.permissions import permission_document_type_view
|
||||
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 Workflow
|
||||
|
||||
from .literals import TEST_WORKFLOW_LABEL, TEST_WORKFLOW_LABEL_EDITED
|
||||
|
||||
|
||||
@override_settings(OCR_AUTO_OCR=False)
|
||||
class WorkflowAPITestCase(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):
|
||||
return Workflow.objects.create(label=TEST_WORKFLOW_LABEL)
|
||||
|
||||
def test_workflow_create_view(self):
|
||||
response = self.client.post(
|
||||
reverse('rest_api:workflow-list'), {
|
||||
'label': TEST_WORKFLOW_LABEL
|
||||
}
|
||||
)
|
||||
|
||||
workflow = Workflow.objects.first()
|
||||
self.assertEqual(Workflow.objects.count(), 1)
|
||||
self.assertEqual(response.data['id'], workflow.pk)
|
||||
|
||||
def test_workflow_create_with_document_type_view(self):
|
||||
response = self.client.post(
|
||||
reverse('rest_api:workflow-list'), {
|
||||
'label': TEST_WORKFLOW_LABEL,
|
||||
'document_types_pk_list': '{}'.format(self.document_type.pk)
|
||||
}
|
||||
)
|
||||
|
||||
workflow = Workflow.objects.first()
|
||||
self.assertEqual(Workflow.objects.count(), 1)
|
||||
self.assertQuerysetEqual(
|
||||
workflow.document_types.all(), (repr(self.document_type),)
|
||||
)
|
||||
self.assertEqual(response.data['id'], workflow.pk)
|
||||
|
||||
def test_workflow_delete_view(self):
|
||||
workflow = self._create_workflow()
|
||||
|
||||
self.client.delete(
|
||||
reverse('rest_api:workflow-detail', args=(workflow.pk,))
|
||||
)
|
||||
|
||||
self.assertEqual(Workflow.objects.count(), 0)
|
||||
|
||||
def test_workflow_detail_view(self):
|
||||
workflow = self._create_workflow()
|
||||
|
||||
response = self.client.get(
|
||||
reverse('rest_api:workflow-detail', args=(workflow.pk,))
|
||||
)
|
||||
|
||||
self.assertEqual(response.data['label'], workflow.label)
|
||||
|
||||
def test_workflow_list_view(self):
|
||||
workflow = self._create_workflow()
|
||||
|
||||
response = self.client.get(reverse('rest_api:workflow-list'))
|
||||
|
||||
self.assertEqual(response.data['results'][0]['label'], workflow.label)
|
||||
|
||||
def test_workflow_put_view(self):
|
||||
workflow = self._create_workflow()
|
||||
|
||||
response = self.client.put(
|
||||
reverse('rest_api:workflow-detail', args=(workflow.pk,)),
|
||||
data={'label': TEST_WORKFLOW_LABEL_EDITED}
|
||||
)
|
||||
|
||||
workflow.refresh_from_db()
|
||||
self.assertEqual(workflow.label, TEST_WORKFLOW_LABEL_EDITED)
|
||||
|
||||
def test_workflow_patch_view(self):
|
||||
workflow = self._create_workflow()
|
||||
|
||||
response = self.client.patch(
|
||||
reverse('rest_api:workflow-detail', args=(workflow.pk,)),
|
||||
data={'label': TEST_WORKFLOW_LABEL_EDITED}
|
||||
)
|
||||
|
||||
workflow.refresh_from_db()
|
||||
self.assertEqual(workflow.label, TEST_WORKFLOW_LABEL_EDITED)
|
||||
|
||||
@@ -2,6 +2,10 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from .api_views import (
|
||||
APIWorkflowDocumentTypeList, APIWorkflowDocumentTypeView,
|
||||
APIWorkflowListView, APIWorkflowView
|
||||
)
|
||||
from .views import (
|
||||
DocumentWorkflowInstanceListView, SetupWorkflowCreateView,
|
||||
SetupWorkflowDeleteView, SetupWorkflowDocumentTypesView,
|
||||
@@ -97,3 +101,21 @@ urlpatterns = patterns(
|
||||
name='setup_workflow_transition_edit'
|
||||
),
|
||||
)
|
||||
|
||||
api_urls = [
|
||||
url(r'^workflows/$', APIWorkflowListView.as_view(), name='workflow-list'),
|
||||
url(
|
||||
r'^workflows/(?P<pk>[0-9]+)/$', APIWorkflowView.as_view(),
|
||||
name='workflow-detail'
|
||||
),
|
||||
url(
|
||||
r'^workflows/(?P<pk>[0-9]+)/document_types/$',
|
||||
APIWorkflowDocumentTypeList.as_view(),
|
||||
name='workflow-document-type-list'
|
||||
),
|
||||
url(
|
||||
r'^workflows/(?P<pk>[0-9]+)/document_types/(?P<document_pk>[0-9]+)/$',
|
||||
APIWorkflowDocumentTypeView.as_view(),
|
||||
name='workflow-document-type-detail'
|
||||
),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user