diff --git a/mayan/apps/acls/urls.py b/mayan/apps/acls/urls.py index 325dccb67a..ea0adb9d8b 100644 --- a/mayan/apps/acls/urls.py +++ b/mayan/apps/acls/urls.py @@ -28,19 +28,19 @@ urlpatterns = [ api_urls = [ url( - r'^object/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/$', + r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/$', APIObjectACLListView.as_view(), name='accesscontrollist-list' ), url( - r'^object/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/$', + r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/$', APIObjectACLView.as_view(), name='accesscontrollist-detail' ), url( - r'^object/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/permissions/$', + r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/permissions/$', APIObjectACLPermissionListView.as_view(), name='accesscontrollist-permission-list' ), url( - r'^object/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/permissions/(?P\d+)/$', + r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/permissions/(?P\d+)/$', APIObjectACLPermissionView.as_view(), name='accesscontrollist-permission-detail' ), ] diff --git a/mayan/apps/checkouts/api_views.py b/mayan/apps/checkouts/api_views.py index f41e76b0e4..6c14a50237 100644 --- a/mayan/apps/checkouts/api_views.py +++ b/mayan/apps/checkouts/api_views.py @@ -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() ) diff --git a/mayan/apps/checkouts/urls.py b/mayan/apps/checkouts/urls.py index bbc29b12c6..59df498eb6 100644 --- a/mayan/apps/checkouts/urls.py +++ b/mayan/apps/checkouts/urls.py @@ -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[0-9]+)/$', APICheckedoutDocumentView.as_view(), + r'^documents/(?P[0-9]+)/checkout_info/$', APICheckedoutDocumentView.as_view(), name='checkedout-document-view' ), ] diff --git a/mayan/apps/document_comments/urls.py b/mayan/apps/document_comments/urls.py index b46f295399..2dd6929abf 100644 --- a/mayan/apps/document_comments/urls.py +++ b/mayan/apps/document_comments/urls.py @@ -25,11 +25,11 @@ urlpatterns = [ api_urls = [ url( - r'^document/(?P[0-9]+)/comments/$', + r'^documents/(?P[0-9]+)/comments/$', APICommentListView.as_view(), name='comment-list' ), url( - r'^document/(?P[0-9]+)/comments/(?P[0-9]+)/$', + r'^documents/(?P[0-9]+)/comments/(?P[0-9]+)/$', APICommentView.as_view(), name='comment-detail' ), ] diff --git a/mayan/apps/document_indexing/urls.py b/mayan/apps/document_indexing/urls.py index 701265903f..3ee1fd0bbd 100644 --- a/mayan/apps/document_indexing/urls.py +++ b/mayan/apps/document_indexing/urls.py @@ -72,12 +72,12 @@ urlpatterns = [ api_urls = [ url( - r'^index/node/(?P[0-9]+)/documents/$', + r'^indexes/node/(?P[0-9]+)/documents/$', APIIndexNodeInstanceDocumentListView.as_view(), name='index-node-documents' ), url( - r'^index/template/(?P[0-9]+)/$', APIIndexTemplateView.as_view(), + r'^indexes/template/(?P[0-9]+)/$', APIIndexTemplateView.as_view(), name='index-template-detail' ), url( @@ -85,12 +85,12 @@ api_urls = [ name='index-detail' ), url( - r'^index/(?P[0-9]+)/template/$', + r'^indexes/(?P[0-9]+)/template/$', APIIndexTemplateListView.as_view(), name='index-template-detail' ), url(r'^indexes/$', APIIndexListView.as_view(), name='index-list'), url( - r'^document/(?P[0-9]+)/indexes/$', + r'^documents/(?P[0-9]+)/indexes/$', APIDocumentIndexListView.as_view(), name='document-index-list' ), ] diff --git a/mayan/apps/document_parsing/api_views.py b/mayan/apps/document_parsing/api_views.py index a074fec935..dd47878ac4 100644 --- a/mayan/apps/document_parsing/api_views.py +++ b/mayan/apps/document_parsing/api_views.py @@ -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() diff --git a/mayan/apps/document_parsing/urls.py b/mayan/apps/document_parsing/urls.py index 334babf9bd..a0916c41dd 100644 --- a/mayan/apps/document_parsing/urls.py +++ b/mayan/apps/document_parsing/urls.py @@ -40,7 +40,8 @@ urlpatterns = [ api_urls = [ url( - r'^page/(?P\d+)/content/$', APIDocumentPageContentView.as_view(), + r'^documents/(?P\d+)/versions/(?P\d+)/pages/(?P\d+)/content/$', + APIDocumentPageContentView.as_view(), name='document-page-content-view' ), ] diff --git a/mayan/apps/document_states/urls.py b/mayan/apps/document_states/urls.py index 97f89d0108..e5fb3e085e 100644 --- a/mayan/apps/document_states/urls.py +++ b/mayan/apps/document_states/urls.py @@ -215,20 +215,20 @@ api_urls = [ APIWorkflowTransitionView.as_view(), name='workflowtransition-detail' ), url( - r'^document/(?P[0-9]+)/workflows/$', + r'^documents/(?P[0-9]+)/workflows/$', APIWorkflowInstanceListView.as_view(), name='workflowinstance-list' ), url( - r'^document/(?P[0-9]+)/workflows/(?P[0-9]+)/$', + r'^documents/(?P[0-9]+)/workflows/(?P[0-9]+)/$', APIWorkflowInstanceView.as_view(), name='workflowinstance-detail' ), url( - r'^document/(?P[0-9]+)/workflows/(?P[0-9]+)/log_entries/$', + r'^documents/(?P[0-9]+)/workflows/(?P[0-9]+)/log_entries/$', APIWorkflowInstanceLogEntryListView.as_view(), name='workflowinstancelogentry-list' ), url( - r'^document_type/(?P[0-9]+)/workflows/$', + r'^document_types/(?P[0-9]+)/workflows/$', APIDocumentTypeWorkflowListView.as_view(), name='documenttype-workflow-list' ), diff --git a/mayan/apps/documents/apps.py b/mayan/apps/documents/apps.py index 35cc5bfcc8..0872ef8039 100644 --- a/mayan/apps/documents/apps.py +++ b/mayan/apps/documents/apps.py @@ -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') diff --git a/mayan/apps/dynamic_search/urls.py b/mayan/apps/dynamic_search/urls.py index 3c78896e71..4558a73d2c 100644 --- a/mayan/apps/dynamic_search/urls.py +++ b/mayan/apps/dynamic_search/urls.py @@ -35,7 +35,7 @@ api_urls = [ name='search-view' ), url( - r'^advanced/(?P[\.\w]+)/$', APIAdvancedSearchView.as_view(), + r'^search/advanced/(?P[\.\w]+)/$', APIAdvancedSearchView.as_view(), name='advanced-search-view' ), ] diff --git a/mayan/apps/ocr/api_views.py b/mayan/apps/ocr/api_views.py index c3a4e20f6c..814dc434a1 100644 --- a/mayan/apps/ocr/api_views.py +++ b/mayan/apps/ocr/api_views.py @@ -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() diff --git a/mayan/apps/ocr/tests/test_api.py b/mayan/apps/ocr/tests/test_api.py index a3cdf7c6cd..b8690c8e21 100644 --- a/mayan/apps/ocr/tests/test_api.py +++ b/mayan/apps/ocr/tests/test_api.py @@ -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): diff --git a/mayan/apps/ocr/urls.py b/mayan/apps/ocr/urls.py index e69f1fd338..536bce2b40 100644 --- a/mayan/apps/ocr/urls.py +++ b/mayan/apps/ocr/urls.py @@ -47,17 +47,17 @@ urlpatterns = [ api_urls = [ url( - r'^document/(?P\d+)/submit/$', APIDocumentOCRView.as_view(), + r'^documents/(?P\d+)/submit/$', APIDocumentOCRView.as_view(), name='document-ocr-submit-view' ), url( - r'^document_version/(?P\d+)/submit/$', + r'^documents/(?P\d+)/versions/(?P\d+)/ocr/$', APIDocumentVersionOCRView.as_view(), name='document-version-ocr-submit-view' ), url( - r'^page/(?P\d+)/content/$', + r'^documents/(?P\d+)/versions/(?P\d+)/pages/(?P\d+)/ocr/$', APIDocumentPageOCRContentView.as_view(), - name='document-page-content-view' + name='document-page-ocr-content-view' ), ] diff --git a/mayan/apps/rest_api/api_views.py b/mayan/apps/rest_api/api_views.py new file mode 100644 index 0000000000..a2791fe70f --- /dev/null +++ b/mayan/apps/rest_api/api_views.py @@ -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() diff --git a/mayan/apps/rest_api/classes.py b/mayan/apps/rest_api/classes.py index 8e359f7108..1fa17d2f7f 100644 --- a/mayan/apps/rest_api/classes.py +++ b/mayan/apps/rest_api/classes.py @@ -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) + ) diff --git a/mayan/apps/rest_api/exceptions.py b/mayan/apps/rest_api/exceptions.py new file mode 100644 index 0000000000..4e0e6ac964 --- /dev/null +++ b/mayan/apps/rest_api/exceptions.py @@ -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 diff --git a/mayan/apps/rest_api/serializers.py b/mayan/apps/rest_api/serializers.py new file mode 100644 index 0000000000..ecfa25faec --- /dev/null +++ b/mayan/apps/rest_api/serializers.py @@ -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() diff --git a/mayan/apps/rest_api/urls.py b/mayan/apps/rest_api/urls.py index cfe3955b66..9f2a4a95d3 100644 --- a/mayan/apps/rest_api/urls.py +++ b/mayan/apps/rest_api/urls.py @@ -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.*)/?$', 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' diff --git a/mayan/apps/rest_api/views.py b/mayan/apps/rest_api/views.py index f5e8118697..8a2064fd36 100644 --- a/mayan/apps/rest_api/views.py +++ b/mayan/apps/rest_api/views.py @@ -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.