diff --git a/docs/releases/2.1.8.rst b/docs/releases/2.1.8.rst index 196d21b395..b315527f7e 100644 --- a/docs/releases/2.1.8.rst +++ b/docs/releases/2.1.8.rst @@ -20,7 +20,7 @@ Changes - Add Django GPG API endpoints for singing keys. - Add API endpoints for the document states app. - Add API endpoints for the messsage of the day (MOTD) app. - +- Add Smart link API endpoints. Removals -------- diff --git a/mayan/apps/linking/api_views.py b/mayan/apps/linking/api_views.py index 931ea56119..dd1ca25476 100644 --- a/mayan/apps/linking/api_views.py +++ b/mayan/apps/linking/api_views.py @@ -6,6 +6,8 @@ from django.shortcuts import get_object_or_404 from rest_framework import generics from acls.models import AccessControlList +from documents.models import Document +from documents.permissions import permission_document_view from permissions import Permission from rest_api.filters import MayanObjectPermissionsFilter from rest_api.permissions import MayanPermission @@ -15,7 +17,159 @@ from .permissions import ( permission_smart_link_create, permission_smart_link_delete, permission_smart_link_edit, permission_smart_link_view ) -from .serializers import SmartLinkConditionSerializer, SmartLinkSerializer +from .serializers import ( + ResolvedSmartLinkDocumentSerializer, ResolvedSmartLinkSerializer, + SmartLinkConditionSerializer, SmartLinkSerializer, + WritableSmartLinkSerializer +) + + +class APIResolvedSmartLinkDocumentListView(generics.ListAPIView): + filter_backends = (MayanObjectPermissionsFilter,) + mayan_object_permissions = {'GET': (permission_document_view,)} + permission_classes = (MayanPermission,) + serializer_class = ResolvedSmartLinkDocumentSerializer + + def get(self, *args, **kwargs): + """ + Returns a list of the smart link documents that apply to the document. + """ + return super(APIResolvedSmartLinkDocumentListView, self).get( + *args, **kwargs + ) + + def get_document(self): + document = get_object_or_404(Document, pk=self.kwargs['pk']) + + try: + Permission.check_permissions( + self.request.user, (permission_document_view,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_document_view, self.request.user, document + ) + + return document + + def get_smart_link(self): + smart_link = get_object_or_404( + SmartLink.objects.get_for(document=self.get_document()), + pk=self.kwargs['smart_link_pk'] + ) + + try: + Permission.check_permissions( + self.request.user, (permission_smart_link_view,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_smart_link_view, self.request.user, smart_link + ) + + return smart_link + + def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ + return { + 'document': self.get_document(), + 'format': self.format_kwarg, + 'request': self.request, + 'smart_link': self.get_smart_link(), + 'view': self + } + + def get_queryset(self): + return self.get_smart_link().get_linked_document_for( + document=self.get_document() + ) + + +class APIResolvedSmartLinkView(generics.RetrieveAPIView): + filter_backends = (MayanObjectPermissionsFilter,) + lookup_url_kwarg = 'smart_link_pk' + mayan_object_permissions = {'GET': (permission_smart_link_view,)} + permission_classes = (MayanPermission,) + serializer_class = ResolvedSmartLinkSerializer + + def get(self, *args, **kwargs): + """ + Return the details of the selected resolved smart link. + """ + return super(APIResolvedSmartLinkView, self).get(*args, **kwargs) + + def get_document(self): + document = get_object_or_404(Document, pk=self.kwargs['pk']) + + try: + Permission.check_permissions( + self.request.user, (permission_document_view,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_document_view, self.request.user, document + ) + + return document + + def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ + return { + 'document': self.get_document(), + 'format': self.format_kwarg, + 'request': self.request, + 'view': self + } + + def get_queryset(self): + return SmartLink.objects.get_for(document=self.get_document()) + + +class APIResolvedSmartLinkListView(generics.ListAPIView): + filter_backends = (MayanObjectPermissionsFilter,) + mayan_object_permissions = {'GET': (permission_smart_link_view,)} + permission_classes = (MayanPermission,) + serializer_class = ResolvedSmartLinkSerializer + + def get(self, *args, **kwargs): + """ + Returns a list of the smart links that apply to the document. + """ + return super(APIResolvedSmartLinkListView, self).get(*args, **kwargs) + + def get_document(self): + document = get_object_or_404(Document, pk=self.kwargs['pk']) + + try: + Permission.check_permissions( + self.request.user, (permission_document_view,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_document_view, self.request.user, document + ) + + return document + + def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ + return { + 'document': self.get_document(), + 'format': self.format_kwarg, + 'request': self.request, + 'view': self + } + + def get_queryset(self): + return SmartLink.objects.filter( + document_types=self.get_document().document_type + ) class APISmartLinkConditionListView(generics.ListCreateAPIView): @@ -139,7 +293,6 @@ class APISmartLinkListView(generics.ListCreateAPIView): mayan_view_permissions = {'POST': (permission_smart_link_create,)} permission_classes = (MayanPermission,) queryset = SmartLink.objects.all() - serializer_class = SmartLinkSerializer def get(self, *args, **kwargs): """ @@ -148,6 +301,12 @@ class APISmartLinkListView(generics.ListCreateAPIView): return super(APISmartLinkListView, self).get(*args, **kwargs) + def get_serializer_class(self): + if self.request.method == 'GET': + return SmartLinkSerializer + else: + return WritableSmartLinkSerializer + def post(self, *args, **kwargs): """ Create a new smart link. @@ -165,7 +324,6 @@ class APISmartLinkView(generics.RetrieveUpdateDestroyAPIView): 'PUT': (permission_smart_link_edit,) } queryset = SmartLink.objects.all() - serializer_class = SmartLinkSerializer def delete(self, *args, **kwargs): """ @@ -181,6 +339,12 @@ class APISmartLinkView(generics.RetrieveUpdateDestroyAPIView): return super(APISmartLinkView, self).get(*args, **kwargs) + def get_serializer_class(self): + if self.request.method == 'GET': + return SmartLinkSerializer + else: + return WritableSmartLinkSerializer + def patch(self, *args, **kwargs): """ Edit the selected smart link. diff --git a/mayan/apps/linking/managers.py b/mayan/apps/linking/managers.py new file mode 100644 index 0000000000..e21ce0364c --- /dev/null +++ b/mayan/apps/linking/managers.py @@ -0,0 +1,8 @@ +from django.db import models + + +class SmartLinkManager(models.Manager): + def get_for(self, document): + return self.filter( + document_types=document.document_type, enabled=True + ) diff --git a/mayan/apps/linking/models.py b/mayan/apps/linking/models.py index 776f84fbe6..22e599d50d 100644 --- a/mayan/apps/linking/models.py +++ b/mayan/apps/linking/models.py @@ -11,6 +11,7 @@ from documents.models import Document, DocumentType from .literals import ( INCLUSION_AND, INCLUSION_CHOICES, INCLUSION_OR, OPERATOR_CHOICES ) +from .managers import SmartLinkManager @python_2_unicode_compatible @@ -29,6 +30,8 @@ class SmartLink(models.Model): DocumentType, verbose_name=_('Document types') ) + objects = SmartLinkManager() + def __str__(self): return self.label diff --git a/mayan/apps/linking/serializers.py b/mayan/apps/linking/serializers.py index 4e94a3dc36..23a0db21ff 100644 --- a/mayan/apps/linking/serializers.py +++ b/mayan/apps/linking/serializers.py @@ -3,6 +3,8 @@ from __future__ import absolute_import, unicode_literals from rest_framework import serializers from rest_framework.reverse import reverse +from documents.serializers import DocumentSerializer + from .models import SmartLink, SmartLinkCondition @@ -48,3 +50,66 @@ class SmartLinkSerializer(serializers.HyperlinkedModelSerializer): 'conditions_url', 'dynamic_label', 'enabled', 'label', 'id', 'url' ) model = SmartLink + + +class ResolvedSmartLinkDocumentSerializer(DocumentSerializer): + resolved_smart_link_url = serializers.SerializerMethodField() + + class Meta(DocumentSerializer.Meta): + fields = DocumentSerializer.Meta.fields + ( + 'resolved_smart_link_url', + ) + read_only_fields = DocumentSerializer.Meta.fields + + def get_resolved_smart_link_url(self, instance): + return reverse( + 'rest_api:resolvedsmartlink-detail', args=( + self.context['document'].pk, self.context['smart_link'].pk + ), request=self.context['request'], + format=self.context['format'] + ) + + +class ResolvedSmartLinkSerializer(SmartLinkSerializer): + resolved_dynamic_label = serializers.SerializerMethodField() + resolved_smart_link_url = serializers.SerializerMethodField() + resolved_documents_url = serializers.SerializerMethodField() + + class Meta(SmartLinkSerializer.Meta): + fields = SmartLinkSerializer.Meta.fields + ( + 'resolved_dynamic_label', 'resolved_smart_link_url', + 'resolved_documents_url' + ) + read_only_fields = SmartLinkSerializer.Meta.fields + + def get_resolved_documents_url(self, instance): + return reverse( + 'rest_api:resolvedsmartlinkdocument-list', + args=(self.context['document'].pk, instance.pk,), + request=self.context['request'], format=self.context['format'] + ) + + def get_resolved_dynamic_label(self, instance): + return instance.get_dynamic_label(document=self.context['document']) + + def get_resolved_smart_link_url(self, instance): + return reverse( + 'rest_api:resolvedsmartlink-detail', + args=(self.context['document'].pk, instance.pk,), + request=self.context['request'], format=self.context['format'] + ) + + +class WritableSmartLinkSerializer(serializers.ModelSerializer): + conditions_url = serializers.HyperlinkedIdentityField( + view_name='rest_api:smartlinkcondition-list' + ) + + class Meta: + extra_kwargs = { + 'url': {'view_name': 'rest_api:smartlink-detail'}, + } + fields = ( + 'conditions_url', 'dynamic_label', 'enabled', 'label', 'id', 'url' + ) + model = SmartLink diff --git a/mayan/apps/linking/tests/literals.py b/mayan/apps/linking/tests/literals.py index e527654e91..6c00f08366 100644 --- a/mayan/apps/linking/tests/literals.py +++ b/mayan/apps/linking/tests/literals.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA = 'label' -TEST_SMART_LINK_CONDITION_EXPRESSION = '\'test\'' +TEST_SMART_LINK_CONDITION_EXPRESSION = 'sample' TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED = '\'test edited\'' TEST_SMART_LINK_CONDITION_OPERATOR = 'icontains' TEST_SMART_LINK_DYNAMIC_LABEL = '{{ document.label }}' diff --git a/mayan/apps/linking/tests/test_api.py b/mayan/apps/linking/tests/test_api.py index 8f334b483b..4e795f830e 100644 --- a/mayan/apps/linking/tests/test_api.py +++ b/mayan/apps/linking/tests/test_api.py @@ -163,6 +163,56 @@ class SmartLinkConditionAPITestCase(APITestCase): operator=TEST_SMART_LINK_CONDITION_OPERATOR ) + def test_resolved_smart_link_detail_view(self): + self._create_document_type() + self._create_smart_link() + self._create_smart_link_condition() + self._create_document() + + response = self.client.get( + reverse( + 'rest_api:resolvedsmartlink-detail', + args=(self.document.pk, self.smart_link.pk) + ) + ) + + self.assertEqual( + response.data['label'], TEST_SMART_LINK_LABEL + ) + + def test_resolved_smart_link_list_view(self): + self._create_document_type() + self._create_smart_link() + self._create_smart_link_condition() + self._create_document() + + response = self.client.get( + reverse( + 'rest_api:resolvedsmartlink-list', args=(self.document.pk,) + ) + ) + + self.assertEqual( + response.data['results'][0]['label'], TEST_SMART_LINK_LABEL + ) + + def test_resolved_smart_link_document_list_view(self): + self._create_document_type() + self._create_smart_link() + self._create_smart_link_condition() + self._create_document() + + response = self.client.get( + reverse( + 'rest_api:resolvedsmartlinkdocument-list', + args=(self.document.pk, self.smart_link.pk) + ) + ) + + self.assertEqual( + response.data['results'][0]['label'], self.document.label + ) + def test_smart_link_condition_create_view(self): self._create_document_type() self._create_smart_link() diff --git a/mayan/apps/linking/urls.py b/mayan/apps/linking/urls.py index 3876fb73aa..272a433e87 100644 --- a/mayan/apps/linking/urls.py +++ b/mayan/apps/linking/urls.py @@ -3,8 +3,9 @@ from __future__ import unicode_literals from django.conf.urls import patterns, url from .api_views import ( - APISmartLinkListView, APISmartLinkView, APISmartLinkConditionListView, - APISmartLinkConditionView + APIResolvedSmartLinkView, APIResolvedSmartLinkDocumentListView, + APIResolvedSmartLinkListView, APISmartLinkListView, APISmartLinkView, + APISmartLinkConditionListView, APISmartLinkConditionView ) from .views import ( DocumentSmartLinkListView, ResolvedSmartLinkView, @@ -68,7 +69,8 @@ urlpatterns = patterns( api_urls = [ url( - r'^smart_links/$', APISmartLinkListView.as_view(), name='smartlink-list' + r'^smart_links/$', APISmartLinkListView.as_view(), + name='smartlink-list' ), url( r'^smart_links/(?P[0-9]+)/$', APISmartLinkView.as_view(), @@ -83,4 +85,19 @@ api_urls = [ APISmartLinkConditionView.as_view(), name='smartlinkcondition-detail' ), + url( + r'^documents/(?P[0-9]+)/resolved_smart_links/$', + APIResolvedSmartLinkListView.as_view(), + name='resolvedsmartlink-list' + ), + url( + r'^documents/(?P[0-9]+)/resolved_smart_links/(?P[0-9]+)/$', + APIResolvedSmartLinkView.as_view(), + name='resolvedsmartlink-detail' + ), + url( + r'^documents/(?P[0-9]+)/resolved_smart_links/(?P[0-9]+)/documents/$', + APIResolvedSmartLinkDocumentListView.as_view(), + name='resolvedsmartlinkdocument-list' + ), ] diff --git a/mayan/apps/linking/views.py b/mayan/apps/linking/views.py index c7e03b0482..a04fbc3efa 100644 --- a/mayan/apps/linking/views.py +++ b/mayan/apps/linking/views.py @@ -174,9 +174,7 @@ class DocumentSmartLinkListView(SmartLinkListView): } def get_smart_link_queryset(self): - return ResolvedSmartLink.objects.filter( - document_types=self.document.document_type, enabled=True - ) + return ResolvedSmartLink.objects.get_for(document=self.document) class SmartLinkCreateView(SingleObjectCreateView):