Switch to a resource and service based API from previous app based one.

Signed-off-by: Michael Price <loneviking72@gmail.com>
This commit is contained in:
Michael Price
2018-03-03 04:03:08 -04:00
committed by Roberto Rosario
parent f3427c7470
commit b4bf9bfaee
19 changed files with 154 additions and 67 deletions

View File

@@ -28,19 +28,19 @@ urlpatterns = [
api_urls = [
url(
r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/$',
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/$',
APIObjectACLListView.as_view(), name='accesscontrollist-list'
),
url(
r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/(?P<pk>\d+)/$',
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/(?P<pk>\d+)/$',
APIObjectACLView.as_view(), name='accesscontrollist-detail'
),
url(
r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/(?P<pk>\d+)/permissions/$',
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/(?P<pk>\d+)/permissions/$',
APIObjectACLPermissionListView.as_view(), name='accesscontrollist-permission-list'
),
url(
r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/(?P<pk>\d+)/permissions/(?P<permission_pk>\d+)/$',
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/(?P<pk>\d+)/permissions/(?P<permission_pk>\d+)/$',
APIObjectACLPermissionView.as_view(), name='accesscontrollist-permission-detail'
),
]

View File

@@ -92,7 +92,7 @@ class APICheckedoutDocumentView(generics.RetrieveDestroyAPIView):
def get_queryset(self):
if self.request.method == 'GET':
filtered_documents = AccessControlList.objects.filter_by_access(
(permission_document_view,), self.request.user,
permission=permission_document_view, user=self.request.user,
queryset=DocumentCheckout.objects.checked_out_documents()
)

View File

@@ -26,11 +26,11 @@ urlpatterns = [
api_urls = [
url(
r'^documents/$', APICheckedoutDocumentListView.as_view(),
r'^checkouts/$', APICheckedoutDocumentListView.as_view(),
name='checkout-document-list'
),
url(
r'^documents/(?P<pk>[0-9]+)/$', APICheckedoutDocumentView.as_view(),
r'^documents/(?P<pk>[0-9]+)/checkout_info/$', APICheckedoutDocumentView.as_view(),
name='checkedout-document-view'
),
]

View File

@@ -25,11 +25,11 @@ urlpatterns = [
api_urls = [
url(
r'^document/(?P<document_pk>[0-9]+)/comments/$',
r'^documents/(?P<document_pk>[0-9]+)/comments/$',
APICommentListView.as_view(), name='comment-list'
),
url(
r'^document/(?P<document_pk>[0-9]+)/comments/(?P<comment_pk>[0-9]+)/$',
r'^documents/(?P<document_pk>[0-9]+)/comments/(?P<comment_pk>[0-9]+)/$',
APICommentView.as_view(), name='comment-detail'
),
]

View File

@@ -72,12 +72,12 @@ urlpatterns = [
api_urls = [
url(
r'^index/node/(?P<pk>[0-9]+)/documents/$',
r'^indexes/node/(?P<pk>[0-9]+)/documents/$',
APIIndexNodeInstanceDocumentListView.as_view(),
name='index-node-documents'
),
url(
r'^index/template/(?P<pk>[0-9]+)/$', APIIndexTemplateView.as_view(),
r'^indexes/template/(?P<pk>[0-9]+)/$', APIIndexTemplateView.as_view(),
name='index-template-detail'
),
url(
@@ -85,12 +85,12 @@ api_urls = [
name='index-detail'
),
url(
r'^index/(?P<pk>[0-9]+)/template/$',
r'^indexes/(?P<pk>[0-9]+)/template/$',
APIIndexTemplateListView.as_view(), name='index-template-detail'
),
url(r'^indexes/$', APIIndexListView.as_view(), name='index-list'),
url(
r'^document/(?P<pk>[0-9]+)/indexes/$',
r'^documents/(?P<pk>[0-9]+)/indexes/$',
APIDocumentIndexListView.as_view(), name='document-index-list'
),
]

View File

@@ -1,9 +1,11 @@
from __future__ import absolute_import, unicode_literals
from django.shortcuts import get_object_or_404
from rest_framework import generics
from rest_framework.response import Response
from documents.models import DocumentPage
from documents.models import Document, DocumentPage
from rest_api.permissions import MayanPermission
from .models import DocumentPageContent
@@ -14,20 +16,24 @@ from .serializers import DocumentPageContentSerializer
class APIDocumentPageContentView(generics.RetrieveAPIView):
"""
Returns the content of the selected document page.
---
GET:
parameters:
- name: pk
paramType: path
type: number
"""
lookup_url_kwarg = 'page_pk'
mayan_object_permissions = {
'GET': (permission_content_view,),
}
permission_classes = (MayanPermission,)
serializer_class = DocumentPageContentSerializer
queryset = DocumentPage.objects.all()
def get_document(self):
return get_object_or_404(Document, pk=self.kwargs['document_pk'])
def get_document_version(self):
return get_object_or_404(
self.get_document().versions.all(), pk=self.kwargs['version_pk']
)
def get_queryset(self):
return self.get_document_version().pages.all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()

View File

@@ -40,7 +40,8 @@ urlpatterns = [
api_urls = [
url(
r'^page/(?P<pk>\d+)/content/$', APIDocumentPageContentView.as_view(),
r'^documents/(?P<document_pk>\d+)/versions/(?P<version_pk>\d+)/pages/(?P<page_pk>\d+)/content/$',
APIDocumentPageContentView.as_view(),
name='document-page-content-view'
),
]

View File

@@ -215,20 +215,20 @@ api_urls = [
APIWorkflowTransitionView.as_view(), name='workflowtransition-detail'
),
url(
r'^document/(?P<pk>[0-9]+)/workflows/$',
r'^documents/(?P<pk>[0-9]+)/workflows/$',
APIWorkflowInstanceListView.as_view(), name='workflowinstance-list'
),
url(
r'^document/(?P<pk>[0-9]+)/workflows/(?P<workflow_pk>[0-9]+)/$',
r'^documents/(?P<pk>[0-9]+)/workflows/(?P<workflow_pk>[0-9]+)/$',
APIWorkflowInstanceView.as_view(), name='workflowinstance-detail'
),
url(
r'^document/(?P<pk>[0-9]+)/workflows/(?P<workflow_pk>[0-9]+)/log_entries/$',
r'^documents/(?P<pk>[0-9]+)/workflows/(?P<workflow_pk>[0-9]+)/log_entries/$',
APIWorkflowInstanceLogEntryListView.as_view(),
name='workflowinstancelogentry-list'
),
url(
r'^document_type/(?P<pk>[0-9]+)/workflows/$',
r'^document_types/(?P<pk>[0-9]+)/workflows/$',
APIDocumentTypeWorkflowListView.as_view(),
name='documenttype-workflow-list'
),

View File

@@ -32,7 +32,7 @@ from events.permissions import permission_events_view
from mayan.celery import app
from mayan_statistics.classes import StatisticNamespace, CharJSLine
from navigation import SourceColumn
from rest_api.classes import APIEndPoint
from rest_api.classes import APIEndPoint, APIResource
from rest_api.fields import DynamicSerializerField
from .dashboard_widgets import (
@@ -107,7 +107,6 @@ from .widgets import (
widget_document_version_page_number
)
class DocumentsApp(MayanAppConfig):
has_tests = True
name = 'documents'
@@ -118,6 +117,9 @@ class DocumentsApp(MayanAppConfig):
from actstream import registry
APIEndPoint(app=self, version_string='1')
APIResource(label=_('Document types'), name='document_types')
APIResource(label=_('Documents'), name='documents')
APIResource(label=_('Trashed documents'), name='trashed_documents')
DeletedDocument = self.get_model('DeletedDocument')
Document = self.get_model('Document')

View File

@@ -35,7 +35,7 @@ api_urls = [
name='search-view'
),
url(
r'^advanced/(?P<search_model>[\.\w]+)/$', APIAdvancedSearchView.as_view(),
r'^search/advanced/(?P<search_model>[\.\w]+)/$', APIAdvancedSearchView.as_view(),
name='advanced-search-view'
),
]

View File

@@ -1,5 +1,7 @@
from __future__ import absolute_import, unicode_literals
from django.shortcuts import get_object_or_404
from rest_framework import generics, status
from rest_framework.response import Response
@@ -26,10 +28,6 @@ class APIDocumentOCRView(generics.GenericAPIView):
Submit a document for OCR.
---
omit_serializer: true
parameters:
- name: pk
paramType: path
type: number
responseMessages:
- code: 202
message: Accepted
@@ -40,12 +38,19 @@ class APIDocumentOCRView(generics.GenericAPIView):
class APIDocumentVersionOCRView(generics.GenericAPIView):
lookup_url_kwarg = 'version_pk'
mayan_object_permissions = {
'POST': (permission_ocr_document,)
}
permission_classes = (MayanPermission,)
queryset = DocumentVersion.objects.all()
def get_document(self):
return get_object_or_404(Document, pk=self.kwargs['document_pk'])
def get_queryset(self):
return self.get_document().versions.all()
def get_serializer_class(self):
return None
@@ -54,10 +59,6 @@ class APIDocumentVersionOCRView(generics.GenericAPIView):
Submit a document version for OCR.
---
omit_serializer: true
parameters:
- name: pk
paramType: path
type: number
responseMessages:
- code: 202
message: Accepted
@@ -70,20 +71,24 @@ class APIDocumentVersionOCRView(generics.GenericAPIView):
class APIDocumentPageOCRContentView(generics.RetrieveAPIView):
"""
Returns the OCR content of the selected document page.
---
GET:
parameters:
- name: pk
paramType: path
type: number
"""
lookup_url_kwarg = 'page_pk'
mayan_object_permissions = {
'GET': (permission_ocr_content_view,),
}
permission_classes = (MayanPermission,)
serializer_class = DocumentPageOCRContentSerializer
queryset = DocumentPage.objects.all()
def get_document(self):
return get_object_or_404(Document, pk=self.kwargs['document_pk'])
def get_document_version(self):
return get_object_or_404(
self.get_document().versions.all(), pk=self.kwargs['version_pk']
)
def get_queryset(self):
return self.get_document_version().pages.all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()

View File

@@ -60,7 +60,7 @@ class OCRAPITestCase(BaseAPITestCase):
def _request_document_version_ocr_submit_view(self):
return self.post(
viewname='rest_api:document-version-ocr-submit-view',
args=(self.document.latest_version.pk,)
args=(self.document.pk, self.document.latest_version.pk,)
)
def test_submit_document_version_no_access(self):
@@ -80,8 +80,11 @@ class OCRAPITestCase(BaseAPITestCase):
def _request_document_page_content_view(self):
return self.get(
viewname='rest_api:document-page-content-view',
args=(self.document.latest_version.pages.first().pk,)
viewname='rest_api:document-page-ocr-content-view',
args=(
self.document.pk, self.document.latest_version.pk,
self.document.latest_version.pages.first().pk,
)
)
def test_get_document_version_page_content_no_access(self):

View File

@@ -47,17 +47,17 @@ urlpatterns = [
api_urls = [
url(
r'^document/(?P<pk>\d+)/submit/$', APIDocumentOCRView.as_view(),
r'^documents/(?P<pk>\d+)/submit/$', APIDocumentOCRView.as_view(),
name='document-ocr-submit-view'
),
url(
r'^document_version/(?P<pk>\d+)/submit/$',
r'^documents/(?P<document_pk>\d+)/versions/(?P<version_pk>\d+)/ocr/$',
APIDocumentVersionOCRView.as_view(),
name='document-version-ocr-submit-view'
),
url(
r'^page/(?P<pk>\d+)/content/$',
r'^documents/(?P<document_pk>\d+)/versions/(?P<version_pk>\d+)/pages/(?P<page_pk>\d+)/ocr/$',
APIDocumentPageOCRContentView.as_view(),
name='document-page-content-view'
name='document-page-ocr-content-view'
),
]

View File

@@ -0,0 +1,18 @@
from __future__ import unicode_literals
from rest_framework import generics
from rest_api.filters import MayanObjectPermissionsFilter
from rest_api.permissions import MayanPermission
from .classes import APIResource
from .serializers import APIResourceSerializer
class APIResourceTypeListView(generics.ListAPIView):
"""
Returns a list of all the available API resources.
"""
serializer_class = APIResourceSerializer
def get_queryset(self):
return APIResource.all()

View File

@@ -5,10 +5,35 @@ from django.conf import settings
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.module_loading import import_string
from .exceptions import APIResourcePatternError
@python_2_unicode_compatible
class APIResource(object):
_registry = {}
@classmethod
def all(cls):
return cls._registry.values()
@classmethod
def get(cls, name):
return cls._registry[name]
def __str__(self):
return force_text(self.name)
def __init__(self, name, label, description=None):
self.label = label
self.name = name
self.description = description
self.__class__._registry[self.name] = self
@python_2_unicode_compatible
class APIEndPoint(object):
_registry = {}
_patterns = []
@classmethod
def get_all(cls):
@@ -48,6 +73,12 @@ class APIEndPoint(object):
def register_urls(self, urlpatterns):
from .urls import urlpatterns as app_urls
app_urls += [
url(r'^%s/' % (self.name or self.app.name), include(urlpatterns)),
]
for url in urlpatterns:
if url.regex.pattern not in self.__class__._patterns:
app_urls.append(url)
self.__class__._patterns.append(url.regex.pattern)
else:
raise APIResourcePatternError(
'App "{}" tried to register API URL pattern "{}", which '
'already exists'.format(self.app.label, url.regex.pattern)
)

View File

@@ -0,0 +1,16 @@
from __future__ import unicode_literals
class APIError(Exception):
"""
Base exception for the API app
"""
pass
class APIResourcePatternError(APIError):
"""
Raised when an app tries to override an existing URL regular expression
pattern
"""
pass

View File

@@ -0,0 +1,9 @@
from __future__ import unicode_literals
from rest_framework import serializers
class APIResourceSerializer(serializers.Serializer):
description = serializers.CharField()
label = serializers.CharField()
name = serializers.CharField()

View File

@@ -2,15 +2,18 @@ from __future__ import unicode_literals
from django.conf.urls import url
from .views import APIBase, APIAppView, BrowseableObtainAuthToken
from .api_views import APIResourceTypeListView
from .views import APIBase, BrowseableObtainAuthToken
urlpatterns = [
]
urlpatterns = []
api_urls = [
url(r'^$', APIBase.as_view(), name='api_root'),
url(r'^api/(?P<path>.*)/?$', APIAppView.as_view(), name='api_app'),
url(
r'^resources/$', APIResourceTypeListView.as_view(),
name='resource-list'
),
url(
r'^auth/token/obtain/$', BrowseableObtainAuthToken.as_view(),
name='auth_token_obtain'

View File

@@ -13,13 +13,6 @@ class APIBase(SwaggerResourcesView):
renderer_classes = (renderers.BrowsableAPIRenderer, renderers.JSONRenderer)
class APIAppView(SwaggerApiView):
"""
Entry points of the selected app.
"""
renderer_classes = (renderers.BrowsableAPIRenderer, renderers.JSONRenderer)
class BrowseableObtainAuthToken(ObtainAuthToken):
"""
Obtain an API authentication token.