Inital work on the document states API

This commit is contained in:
Roberto Rosario
2017-02-08 17:03:36 -04:00
parent 651e370191
commit 146459d5bc
6 changed files with 527 additions and 1 deletions

View 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)

View File

@@ -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'
)

View 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

View File

@@ -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'

View 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)

View File

@@ -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'
),
]