diff --git a/mayan/apps/document_comments/api_views.py b/mayan/apps/document_comments/api_views.py new file mode 100644 index 0000000000..2ce872e2db --- /dev/null +++ b/mayan/apps/document_comments/api_views.py @@ -0,0 +1,120 @@ +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.models import Document +from permissions import Permission + +from .permissions import ( + permission_comment_create, permission_comment_delete, + permission_comment_view +) +from .serializers import CommentSerializer, WritableCommentSerializer + + +class APICommentListView(generics.ListCreateAPIView): + def get(self, *args, **kwargs): + """ + Returns a list of all the document comments. + """ + return super(APICommentListView, self).get(*args, **kwargs) + + def get_document(self): + if self.request.method == 'GET': + permission_required = permission_comment_view + else: + permission_required = permission_comment_create + + document = get_object_or_404(Document, pk=self.kwargs['document_pk']) + + try: + Permission.check_permissions( + self.request.user, (permission_required,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_required, self.request.user, document + ) + + return document + + def get_queryset(self): + return self.get_document().comments.all() + + def get_serializer_class(self): + if self.request.method == 'GET': + return CommentSerializer + else: + return WritableCommentSerializer + + 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 post(self, *args, **kwargs): + """ + Create a new document comment. + """ + return super(APICommentListView, self).post(*args, **kwargs) + + +class APICommentView(generics.RetrieveDestroyAPIView): + lookup_url_kwarg = 'comment_pk' + serializer_class = CommentSerializer + + def delete(self, request, *args, **kwargs): + """ + Delete the selected document comment. + """ + + return super(APICommentView, self).delete(request, *args, **kwargs) + + def get(self, *args, **kwargs): + """ + Returns the details of the selected document comment. + """ + + return super(APICommentView, self).get(*args, **kwargs) + + def get_document(self): + if self.request.method == 'GET': + permission_required = permission_comment_view + else: + permission_required = permission_comment_delete + + document = get_object_or_404(Document, pk=self.kwargs['document_pk']) + + try: + Permission.check_permissions( + self.request.user, (permission_required,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_required, self.request.user, document + ) + + return document + + def get_queryset(self): + return self.get_document().comments.all() + + def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ + return { + 'format': self.format_kwarg, + 'request': self.request, + 'view': self + } diff --git a/mayan/apps/document_comments/apps.py b/mayan/apps/document_comments/apps.py index c4d5f3dfd4..905af2c7cf 100644 --- a/mayan/apps/document_comments/apps.py +++ b/mayan/apps/document_comments/apps.py @@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from acls import ModelPermission from common import MayanAppConfig, menu_facet, menu_object, menu_sidebar from navigation import SourceColumn +from rest_api.classes import APIEndPoint from .links import ( link_comment_add, link_comment_delete, link_comments_for_document @@ -20,11 +21,14 @@ class DocumentCommentsApp(MayanAppConfig): app_namespace = 'comments' app_url = 'comments' name = 'document_comments' + test = True verbose_name = _('Document comments') def ready(self): super(DocumentCommentsApp, self).ready() + APIEndPoint(app=self, version_string='1') + Document = apps.get_model( app_label='documents', model_name='Document' ) diff --git a/mayan/apps/document_comments/serializers.py b/mayan/apps/document_comments/serializers.py new file mode 100644 index 0000000000..4090cdadfd --- /dev/null +++ b/mayan/apps/document_comments/serializers.py @@ -0,0 +1,71 @@ +from __future__ import unicode_literals + +from rest_framework import serializers +from rest_framework.reverse import reverse + +from documents.serializers import DocumentSerializer +from user_management.serializers import UserSerializer + +from .models import Comment + + +class CommentSerializer(serializers.HyperlinkedModelSerializer): + document = DocumentSerializer(read_only=True) + document_comments_url = serializers.SerializerMethodField() + url = serializers.SerializerMethodField() + user = UserSerializer(read_only=True) + + class Meta: + fields = ( + 'comment', 'document', 'document_comments_url', 'id', + 'submit_date', 'url', 'user' + ) + model = Comment + + def get_document_comments_url(self, instance): + return reverse( + 'rest_api:comment-list', args=( + instance.document.pk, + ), request=self.context['request'], format=self.context['format'] + ) + + def get_url(self, instance): + return reverse( + 'rest_api:comment-detail', args=( + instance.document.pk, instance.pk + ), request=self.context['request'], format=self.context['format'] + ) + + +class WritableCommentSerializer(serializers.ModelSerializer): + document = DocumentSerializer(read_only=True) + document_comments_url = serializers.SerializerMethodField() + url = serializers.SerializerMethodField() + user = UserSerializer(read_only=True) + + class Meta: + fields = ( + 'comment', 'document', 'document_comments_url', 'id', + 'submit_date', 'url', 'user' + ) + model = Comment + read_only_fields = ('document',) + + def create(self, validated_data): + validated_data['document'] = self.context['document'] + validated_data['user'] = self.context['request'].user + return super(WritableCommentSerializer, self).create(validated_data) + + def get_document_comments_url(self, instance): + return reverse( + 'rest_api:comment-list', args=( + instance.document.pk, + ), request=self.context['request'], format=self.context['format'] + ) + + def get_url(self, instance): + return reverse( + 'rest_api:comment-detail', args=( + instance.document.pk, instance.pk + ), request=self.context['request'], format=self.context['format'] + ) diff --git a/mayan/apps/document_comments/tests/__init__.py b/mayan/apps/document_comments/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mayan/apps/document_comments/tests/literals.py b/mayan/apps/document_comments/tests/literals.py new file mode 100644 index 0000000000..489cba38be --- /dev/null +++ b/mayan/apps/document_comments/tests/literals.py @@ -0,0 +1,3 @@ +from __future__ import unicode_literals + +TEST_COMMENT_TEXT = 'test comment text' diff --git a/mayan/apps/document_comments/tests/test_api.py b/mayan/apps/document_comments/tests/test_api.py new file mode 100644 index 0000000000..72dd645028 --- /dev/null +++ b/mayan/apps/document_comments/tests/test_api.py @@ -0,0 +1,97 @@ +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.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 Comment + +from .literals import TEST_COMMENT_TEXT + + +@override_settings(OCR_AUTO_OCR=False) +class CommentAPITestCase(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_comment(self): + return self.document.comments.create( + comment=TEST_COMMENT_TEXT, user=self.admin_user + ) + + def test_comment_create_view(self): + response = self.client.post( + reverse( + 'rest_api:comment-list', args=(self.document.pk,) + ), { + 'comment': TEST_COMMENT_TEXT + } + ) + + self.assertEqual(response.status_code, 201) + comment = Comment.objects.first() + self.assertEqual(Comment.objects.count(), 1) + self.assertEqual(response.data['id'], comment.pk) + + def test_comment_delete_view(self): + comment = self._create_comment() + + self.client.delete( + reverse( + 'rest_api:comment-detail', args=(self.document.pk, comment.pk,) + ) + ) + + self.assertEqual(Comment.objects.count(), 0) + + def test_comment_detail_view(self): + comment = self._create_comment() + + response = self.client.get( + reverse( + 'rest_api:comment-detail', args=(self.document.pk, comment.pk,) + ) + ) + + self.assertEqual(response.data['comment'], comment.comment) + + def test_comment_list_view(self): + comment = self._create_comment() + + response = self.client.get( + reverse('rest_api:comment-list', args=(self.document.pk,)) + ) + + self.assertEqual( + response.data['results'][0]['comment'], comment.comment + ) diff --git a/mayan/apps/document_comments/urls.py b/mayan/apps/document_comments/urls.py index b2c68b989e..b538962d34 100644 --- a/mayan/apps/document_comments/urls.py +++ b/mayan/apps/document_comments/urls.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django.conf.urls import patterns, url +from .api_views import APICommentListView, APICommentView from .views import ( DocumentCommentCreateView, DocumentCommentDeleteView, DocumentCommentListView @@ -22,3 +23,14 @@ urlpatterns = patterns( DocumentCommentListView.as_view(), name='comments_for_document' ), ) + +api_urls = [ + url( + r'^document/(?P[0-9]+)/comments/$', + APICommentListView.as_view(), name='comment-list' + ), + url( + r'^document/(?P[0-9]+)/comments/(?P[0-9]+)/$', + APICommentView.as_view(), name='comment-detail' + ), +]