Convert the API URL system from an App based one

to a resource based one.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2017-05-12 17:53:44 -04:00
parent 105eab0740
commit acdc7dca48
30 changed files with 310 additions and 108 deletions

View File

@@ -71,7 +71,8 @@ Backward incompatible changes
Bugs fixed or issues closed
===========================
* `GitLab issue #378 <https://gitlab.com/mayan-edms/mayan-edms/issues/378>`_ Add metadata widget changes from @Macrobb
* `GitLab issue #366 <https://gitlab.com/mayan-edms/mayan-edms/issues/366>`_ Proofread documentation
* `GitLab issue #379 <https://gitlab.com/mayan-edms/mayan-edms/issues/379>`_ Add new document version list view permission.
* `GitLab issue #379 <https://gitlab.com/mayan-edms/mayan-edms/issues/379>`_ Add new document version list view permission.

View File

@@ -3,7 +3,8 @@ Advanced deployment
===================
Mayan EDMS should be deployed like any other Django_ project and
preferably using virtualenv_.
preferably using virtualenv_. Below are some ways to deploy and use Mayan EDMS.
Do not use more than one method.
Being a Django_ and a Python_ project, familiarity with these technologies is
recommended to better understand why Mayan EDMS does some of the things it
@@ -58,7 +59,7 @@ to /usr/bin/ with ...
sudo ln -s /opt/local/bin/tesseract /usr/bin/tesseract
... alternatively set the paths in the ``settings/locals.py``
Alternatively, set the paths in the ``settings/locals.py``
.. code-block:: python
@@ -76,9 +77,9 @@ With Homebrew installed run the command:
Set the Binary paths
********************
Mayan EDMS by default will look in /usr/bin/ for the binary files it needs
so either you can symlink the binaries installed via brew in /usr/local/bin/
to /usr/bin/ with ...
Mayan EDMS by default will look in /usr/bin/ for the binary files it needs.
You can symlink the binaries installed via brew in /usr/local/bin/
to /usr/bin/ with:
.. code-block:: bash
@@ -87,7 +88,7 @@ to /usr/bin/ with ...
sudo ln -s /usr/local/bin/pdftotext /usr/bin/pdftotext && \
sudo ln -s /usr/local/bin/gs /usr/bin/gs
... alternatively set the paths in the ``settings/locals.py``
Alternatively, set the paths in the ``settings/locals.py``
.. code-block:: python
@@ -265,15 +266,18 @@ Make the installation directory readable and writable by the webserver user::
chown www-data:www-data /usr/share/mayan-edms -R
Restart the services::
Enable and restart the services [1_]::
systemctl enable supervisor
systemctl restart supervisor
systemctl restart nginx
[1]: https://bugs.launchpad.net/ubuntu/+source/supervisor/+bug/1594740
.. _Debian: http://www.debian.org/
.. _Django: http://www.djangoproject.com/
.. _Python: http://www.python.org/
.. _SQLite: https://www.sqlite.org/
.. _Ubuntu: http://www.ubuntu.com/
.. _virtualenv: http://www.virtualenv.org/en/latest/index.html
.. _1: https://bugs.launchpad.net/ubuntu/+source/supervisor/+bug/1594740

View File

@@ -16,8 +16,8 @@ request on GitLab_.
Project philosophies
--------------------
How to think about Mayan EDMS when doing changes or adding new features,
why things are the way they are in Mayan EDMS.
How to think about Mayan EDMS when doing changes or adding new features;
why things are the way they are in Mayan EDMS:
- Functionality must be as market/sector independent as possible, code for the
95% of use cases.
@@ -36,7 +36,7 @@ why things are the way they are in Mayan EDMS.
not viable/mature/efficient.
- Each app is as independent and self contained as possible. Exceptions, the
basic requirements: navigation, permissions, common, main.
- If an app is meant to be used by more than one other app it should be as
- If an app is meant to be used by more than one other app, it should be as
generic as possible in regard to the project and another app will bridge the functionality.
- Example: since indexing (document_indexing) only applies to documents, the
@@ -48,7 +48,7 @@ Coding conventions
Follow PEP8
~~~~~~~~~~~
Whenever possible, but don't obsess over things like line length.
Whenever possible, but don't obsess over things like line length:
.. code-block:: bash
@@ -103,9 +103,9 @@ Example:
)
from .models import Index, IndexInstanceNode, DocumentRenameCount
All local app module imports are in relative form, local app module name is to
All local app module imports are in relative form. Local app module name is to
be referenced as little as possible, unless required by a specific feature,
trick, restriction, ie: Runtime modification of the module's attributes.
trick, restriction (e.g., Runtime modification of the module's attributes).
Incorrect:
@@ -128,7 +128,7 @@ Dependencies
Mayan EDMS apps follow a hierarchical model of dependency. Apps import from
their parents or siblings, never from their children. Think plugins. A parent
app must never assume anything about a possible existing child app. The
documents app and the Document model are the basic entities they must never
documents app and the Document model are the basic entities; they must never
import anything else. The common and main apps are the base apps.

View File

@@ -30,7 +30,7 @@ Features
* Dynamic default values for metadata.
* Metadata fields can have an initial value, which can be static or determined
by an user provided template code snippet.
by a template code snippet provided by the user.
* Documents can be uploaded from different sources.
@@ -68,7 +68,7 @@ Features
* Multi page document support.
* Multiple page PDFs and TIFFs files are supported.
* Multiple page PDF and TIFF files are supported.
* Automatic OCR processing.

View File

@@ -2,21 +2,20 @@
Transformations
===============
Transformation are persistent manipulations to the previews of the stored
Transformations are persistent manipulations to the previews of the stored
documents. For example: a scanning equipment may only produce landscape PDFs.
In this case an useful transformation for that document source would be to
rotate all documents scanned by 270 degrees after being uploaded, this way
whenever a document is uploaded from that scanner it will appear in portrait
orientation. In this case add a this transformation to the Mayan EDMS source
that is connected to that device this way all pages scanned via that source
with inherit the transformation as they are created.
In this case a useful transformation for that document source would be to rotate
all scanned documents by 270 degrees after being uploaded. By adding this
transformation to the Mayan EDMS source that is connected to the scanner, all
pages scanned via that source will inherit the transformation as they are
created. The result is that whenever a document is uploaded from that scanner,
it will appear in portrait orientation, instead of landscape orientation.
Transformations can also be added to existing documents, by clicking on a
document's page, then clicking on "transformations". In this view the Actions
menu will have a new option that reads "Create new transformation". At the
moment the rotation, zoom, crop, and resize transformations are available.
Once the document image has been corrected resubmit it for OCR for improved
results.
Transformations can also be added to existing documents by clicking on a
document's page and then clicking on "transformations". In this view the Actions
menu will have a new option that reads "Create new transformation". Currently,
the available transformations are: rotation, zoom, crop, and resize. Once the
document image has been corrected, resubmit it for OCR for improved results.
Transformations are not destructive and do not physically modify the document
file, they just modify the document's graphical representation.

View File

@@ -115,8 +115,10 @@ class AccessControlListManager(models.Manager):
def filter_by_access(self, permission, user, queryset):
if user.is_superuser or user.is_staff:
logger.debug('Unfiltered queryset returned to user "%s" as superuser or staff',
user)
logger.debug(
'Unfiltered queryset returned to user "%s" as superuser '
'or staff', user
)
return queryset
try:

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

@@ -1,20 +1,13 @@
from __future__ import absolute_import, unicode_literals
import pytz
from django.shortcuts import get_object_or_404
from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework import generics
from acls.models import AccessControlList
from documents.models import Document
from documents.permissions import permission_document_view
from .models import DocumentCheckout
from .permissions import (
permission_document_checkout, permission_document_checkin,
permission_document_checkin_override
permission_document_checkin, permission_document_checkin_override
)
from .serializers import (
DocumentCheckoutSerializer, NewDocumentCheckoutSerializer
@@ -47,12 +40,23 @@ class APICheckedoutDocumentListView(generics.ListCreateAPIView):
APICheckedoutDocumentListView, self
).get(request, *args, **kwargs)
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'format': self.format_kwarg,
'request': self.request,
'view': self
}
'''
def post(self, request, *args, **kwargs):
"""
Checkout a document.
"""
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
serializer = self.get_serializer(data=request.data, files=request.file)
if serializer.is_valid():
document = get_object_or_404(
@@ -83,6 +87,7 @@ class APICheckedoutDocumentListView(generics.ListCreateAPIView):
return Response(status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
'''
class APICheckedoutDocumentView(generics.RetrieveDestroyAPIView):

View File

@@ -57,7 +57,10 @@ class CheckoutsApp(MayanAppConfig):
Document.add_to_class(
'check_in',
lambda document, user=None: DocumentCheckout.objects.check_in_document(document, user)
lambda document,
user=None: DocumentCheckout.objects.check_in_document(
document, user
)
)
Document.add_to_class(
'checkout_info',

View File

@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
class DocumentCheckoutManager(models.Manager):
def checkout_document(self, document, expiration_datetime, user, block_new_version=True):
self.create(
return self.create(
document=document, expiration_datetime=expiration_datetime,
user=user, block_new_version=block_new_version
)

View File

@@ -1,15 +1,19 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from acls.models import AccessControlList
from documents.models import Document
from documents.serializers import DocumentSerializer
from .models import DocumentCheckout
from .permissions import permission_document_checkout
class DocumentCheckoutSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
# Hide this import otherwise strange circular import error occur
from documents.serializers import DocumentSerializer
super(DocumentCheckoutSerializer, self).__init__(*args, **kwargs)
self.fields['document'] = DocumentSerializer()
@@ -17,7 +21,33 @@ class DocumentCheckoutSerializer(serializers.ModelSerializer):
model = DocumentCheckout
class NewDocumentCheckoutSerializer(serializers.Serializer):
document = serializers.IntegerField()
expiration_datetime = serializers.DateTimeField()
class NewDocumentCheckoutSerializer(serializers.ModelSerializer):
block_new_version = serializers.BooleanField()
document_pk = serializers.IntegerField(
help_text=_('Primary key of the document to be checked out.'),
write_only=True
)
expiration_datetime = serializers.DateTimeField()
class Meta:
fields = (
'block_new_version', 'document', 'document_pk',
'expiration_datetime', 'id'
)
model = DocumentCheckout
read_only_fields = ('document',)
write_only_fields = ('document_pk',)
def create(self, validated_data):
document = Document.objects.get(pk=validated_data.pop('document_pk'))
AccessControlList.objects.check_access(
permissions=permission_document_checkout,
user=self.context['request'].user, obj=document
)
validated_data['document'] = document
validated_data['user'] = self.context['request'].user
return super(NewDocumentCheckoutSerializer, self).create(
validated_data
)

View File

@@ -0,0 +1,75 @@
from __future__ import unicode_literals
import datetime
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.test import override_settings
from django.utils.encoding import force_text
from django.utils.timezone import now
from rest_framework.test import APITestCase
from documents.models import DocumentType
from documents.tests 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 DocumentCheckout
@override_settings(OCR_AUTO_OCR=False)
class CheckoutAPITestCase(APITestCase):
def setUp(self):
super(CheckoutAPITestCase, self).setUp()
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):
self.document_type.delete()
super(CheckoutAPITestCase, self).tearDown()
def test_document_checkout_get_view(self):
expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True
)
response = self.client.get(reverse('rest_api:checkout-document-list'))
self.assertEqual(
response.data['results'][0]['document']['uuid'],
force_text(self.document.uuid)
)
def test_document_checkout_post_view(self):
response = self.client.post(
reverse('rest_api:checkout-document-list'), data={
'document_pk': self.document.pk,
'expiration_datetime': '2099-01-01T12:00'
}
)
self.assertEqual(response.status_code, 201)
self.assertEqual(
DocumentCheckout.objects.first().document, self.document
)

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'^checkouts/(?P<pk>[0-9]+)/$', 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

@@ -164,20 +164,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

@@ -27,7 +27,7 @@ from events.links import link_events_for_object
from events.permissions import permission_events_view
from mayan.celery import app
from navigation import SourceColumn
from rest_api.classes import APIEndPoint
from rest_api.classes import APIEndPoint, APIResource
from rest_api.fields import DynamicSerializerField
from statistics.classes import StatisticNamespace, CharJSLine
@@ -91,6 +91,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

@@ -29,7 +29,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

@@ -20,10 +20,10 @@ urlpatterns = [
]
api_urls = [
url(r'^types/$', APIEventTypeListView.as_view(), name='event-type-list'),
url(r'^event_types/$', APIEventTypeListView.as_view(), name='event-type-list'),
url(r'^events/$', APIEventListView.as_view(), name='event-list'),
url(
r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/events/$',
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/events/$',
APIObjectEventListView.as_view(), name='object-event-list'
),
]

View File

@@ -60,7 +60,7 @@ api_urls = [
),
url(r'^folders/$', APIFolderListView.as_view(), name='folder-list'),
url(
r'^document/(?P<pk>[0-9]+)/folders/$',
r'^documents/(?P<pk>[0-9]+)/folders/$',
APIDocumentFolderListView.as_view(), name='document-folder-list'
),
]

View File

@@ -3,6 +3,8 @@ from __future__ import absolute_import, unicode_literals
from rest_framework import generics, status
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from documents.models import Document, DocumentPage, DocumentVersion
from rest_api.permissions import MayanPermission
@@ -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,25 @@ class APIDocumentVersionOCRView(generics.GenericAPIView):
class APIDocumentPageContentView(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 = 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

@@ -63,7 +63,9 @@ class OCRAPITestCase(BaseAPITestCase):
response = self.client.post(
reverse(
'rest_api:document-version-ocr-submit-view',
args=(self.document.latest_version.pk,)
args=(
self.document.pk, self.document.latest_version.pk,
)
)
)
@@ -77,7 +79,10 @@ class OCRAPITestCase(BaseAPITestCase):
response = self.client.get(
reverse(
'rest_api:document-page-content-view',
args=(self.document.latest_version.pages.first().pk,)
args=(
self.document.pk, self.document.latest_version.pk,
self.document.latest_version.pages.first().pk,
)
),
)

View File

@@ -43,16 +43,16 @@ urlpatterns = [
api_urls = [
url(
r'^document/(?P<pk>\d+)/submit/$', APIDocumentOCRView.as_view(),
r'^documents/(?P<pk>\d+)/ocr/$', 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/$', APIDocumentPageContentView.as_view(),
r'^documents/(?P<document_pk>\d+)/versions/(?P<version_pk>\d+)/pages/(?P<page_pk>\d+)/ocr/$', APIDocumentPageContentView.as_view(),
name='document-page-content-view'
),
]

View File

@@ -30,5 +30,4 @@ api_urls = [
url(r'^permissions/$', APIPermissionList.as_view(), name='permission-list'),
url(r'^roles/$', APIRoleListView.as_view(), name='role-list'),
url(r'^roles/(?P<pk>[0-9]+)/$', APIRoleView.as_view(), name='role-detail'),
url(r'^$', APIPermissionList.as_view(), name='permission-list'),
]

View File

@@ -0,0 +1,19 @@
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

@@ -4,9 +4,33 @@ from django.conf.urls import include, url
from django.conf import settings
from django.utils.module_loading import import_string
from .exceptions import APIResourcePatternError
class APIResource(object):
_registry = {}
@classmethod
def all(cls):
return cls._registry.values()
@classmethod
def get(cls, name):
return cls._registry[name]
def __unicode__(self):
return unicode(self.name)
def __init__(self, name, label, description=None):
self.label = label
self.name = name
self.description = description
self.__class__._registry[self.name] = self
class APIEndPoint(object):
_registry = {}
_patterns = []
@classmethod
def get_all(cls):
@@ -46,6 +70,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.