Add support for editing document comments

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2019-05-09 22:29:07 -04:00
parent cbd51c5f26
commit f73179a600
17 changed files with 348 additions and 43 deletions

View File

@@ -250,6 +250,7 @@
* Update tag document list and the document tag list
views to require the view permissions for both objects.
* Install and server static content to and from the image.
* Add support for editing document comments.
3.1.11 (2019-04-XX)
===================

View File

@@ -560,7 +560,7 @@ Other changes
* Install and server static content to and from the image.
Remove installation of static content from the setup
and upgrade stages.
* Add support for editing document comments.
Removals

View File

@@ -9,7 +9,7 @@ from mayan.apps.documents.models import Document
from .permissions import (
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
permission_document_comment_edit, permission_document_comment_view
)
from .serializers import CommentSerializer, WritableCommentSerializer
@@ -66,7 +66,7 @@ class APICommentListView(generics.ListCreateAPIView):
return context
class APICommentView(generics.RetrieveDestroyAPIView):
class APICommentView(generics.RetrieveUpdateDestroyAPIView):
"""
delete: Delete the selected document comment.
get: Returns the details of the selected document comment.
@@ -75,10 +75,12 @@ class APICommentView(generics.RetrieveDestroyAPIView):
serializer_class = CommentSerializer
def get_document(self):
if self.request.method == 'GET':
permission_required = permission_document_comment_view
else:
if self.request.method == 'DELETE':
permission_required = permission_document_comment_delete
elif self.request.method in ('PATCH', 'PUT'):
permission_required = permission_document_comment_edit
else:
permission_required = permission_document_comment_view
document = get_object_or_404(
klass=Document, pk=self.kwargs['document_pk']

View File

@@ -11,20 +11,22 @@ from mayan.apps.common.menus import (
from mayan.apps.documents.search import document_page_search, document_search
from mayan.apps.events.classes import ModelEventType
from mayan.apps.events.links import (
link_events_for_object,
link_events_for_object, link_object_event_types_user_subcriptions_list
)
from mayan.apps.events.permissions import permission_events_view
from mayan.apps.navigation.classes import SourceColumn
from .events import (
event_document_comment_created, event_document_comment_deleted
event_document_comment_created, event_document_comment_deleted,
event_document_comment_edited
)
from .links import (
link_comment_add, link_comment_delete, link_comments_for_document
link_comment_add, link_comment_delete, link_comment_edit,
link_comments_for_document
)
from .permissions import (
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
permission_document_comment_edit, permission_document_comment_view
)
@@ -46,9 +48,15 @@ class DocumentCommentsApp(MayanAppConfig):
Comment = self.get_model(model_name='Comment')
ModelEventType.register(
model=Comment, event_types=(
event_document_comment_edited,
)
)
ModelEventType.register(
model=Document, event_types=(
event_document_comment_created, event_document_comment_deleted
event_document_comment_created, event_document_comment_deleted,
event_document_comment_edited
)
)
@@ -60,7 +68,9 @@ class DocumentCommentsApp(MayanAppConfig):
)
ModelPermission.register(
model=Document, permissions=(
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_create,
permission_document_comment_delete,
permission_document_comment_edit,
permission_document_comment_view
)
)
@@ -88,6 +98,7 @@ class DocumentCommentsApp(MayanAppConfig):
menu_list_facet.bind_links(
links=(
link_events_for_object,
link_object_event_types_user_subcriptions_list
), sources=(Comment,)
)
@@ -95,12 +106,13 @@ class DocumentCommentsApp(MayanAppConfig):
links=(link_comment_add,),
sources=(
'comments:comments_for_document', 'comments:comment_add',
'comments:comment_delete', 'comments:comment_multiple_delete'
'comments:comment_delete', 'comments:comment_details',
'comments:comment_edit', 'comments:comment_multiple_delete'
)
)
menu_object.bind_links(
links=(link_comment_delete,), sources=(Comment,)
links=(link_comment_delete, link_comment_edit), sources=(Comment,)
)
registry.register(Comment)

View File

@@ -14,3 +14,6 @@ event_document_comment_created = namespace.add_event_type(
event_document_comment_deleted = namespace.add_event_type(
label=_('Document comment deleted'), name='delete'
)
event_document_comment_edited = namespace.add_event_type(
label=_('Document comment edited'), name='edited'
)

View File

@@ -0,0 +1,18 @@
from __future__ import absolute_import, unicode_literals
from django import forms
from mayan.apps.common.forms import DetailForm
from .models import Comment
class DocumentCommentDetailForm(DetailForm):
class Meta:
fields = ('comment',)
extra_fields = (
{'field': 'submit_date', 'widget': forms.widgets.DateTimeInput},
{'field': 'user'},
)
model = Comment

View File

@@ -7,4 +7,5 @@ icon_comment_add = Icon(
secondary_symbol='plus'
)
icon_comment_delete = Icon(driver_name='fontawesome', symbol='times')
icon_comment_edit = Icon(driver_name='fontawesome', symbol='pencil-alt')
icon_comments_for_document = Icon(driver_name='fontawesome', symbol='comment')

View File

@@ -4,10 +4,13 @@ from django.utils.translation import ugettext_lazy as _
from mayan.apps.navigation.classes import Link
from .icons import icon_comment_add, icon_comment_delete, icon_comments_for_document
from .icons import (
icon_comment_add, icon_comment_delete, icon_comment_edit,
icon_comments_for_document
)
from .permissions import (
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
permission_document_comment_edit, permission_document_comment_view
)
link_comment_add = Link(
@@ -20,6 +23,11 @@ link_comment_delete = Link(
permissions=(permission_document_comment_delete,), tags='dangerous',
text=_('Delete'), view='comments:comment_delete',
)
link_comment_edit = Link(
args='object.pk', icon_class=icon_comment_edit,
permissions=(permission_document_comment_edit,),
text=_('Edit'), view='comments:comment_edit',
)
link_comments_for_document = Link(
args='resolved_object.pk', icon_class=icon_comments_for_document,
permissions=(permission_document_comment_view,), text=_('Comments'),

View File

@@ -4,13 +4,15 @@ import logging
from django.conf import settings
from django.db import models, transaction
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from mayan.apps.documents.models import Document
from .events import (
event_document_comment_created, event_document_comment_deleted
event_document_comment_created, event_document_comment_deleted,
event_document_comment_edited
)
logger = logging.getLogger(__name__)
@@ -53,6 +55,11 @@ class Comment(models.Model):
actor=_user, target=self.document
)
def get_absolute_url(self):
return reverse(
viewname='comments:comment_details', kwargs={'pk': self.pk}
)
def save(self, *args, **kwargs):
_user = kwargs.pop('_user', None) or self.user
created = not self.pk
@@ -61,5 +68,9 @@ class Comment(models.Model):
super(Comment, self).save(*args, **kwargs)
if created:
event_document_comment_created.commit(
action_object=self, actor=_user, target=self.document,
action_object=self.document, actor=_user, target=self,
)
else:
event_document_comment_edited.commit(
action_object=self.document, actor=_user, target=self,
)

View File

@@ -12,6 +12,9 @@ permission_document_comment_create = namespace.add_permission(
permission_document_comment_delete = namespace.add_permission(
label=_('Delete comments'), name='comment_delete'
)
permission_document_comment_edit = namespace.add_permission(
label=_('Edit comments'), name='comment_edit'
)
permission_document_comment_view = namespace.add_permission(
label=_('View comments'), name='comment_view'
)

View File

@@ -1,3 +1,4 @@
from __future__ import unicode_literals
TEST_COMMENT_TEXT = 'test comment text'
TEST_COMMENT_TEXT_EDITED = 'test comment text edited'

View File

@@ -1,6 +1,6 @@
from __future__ import unicode_literals
from .literals import TEST_COMMENT_TEXT
from .literals import TEST_COMMENT_TEXT, TEST_COMMENT_TEXT_EDITED
class DocumentCommentTestMixin(object):
@@ -24,3 +24,19 @@ class DocumentCommentViewTestMixin(object):
'pk': self.test_document_comment.pk
},
)
def _request_test_comment_edit_view(self):
return self.post(
viewname='comments:comment_edit', kwargs={
'pk': self.test_document_comment.pk,
}, data={
'comment': TEST_COMMENT_TEXT_EDITED
}
)
def _request_test_comment_list_view(self):
return self.get(
viewname='comments:comments_for_document', kwargs={
'pk': self.test_document.pk,
}
)

View File

@@ -8,17 +8,17 @@ from mayan.apps.rest_api.tests import BaseAPITestCase
from ..models import Comment
from ..permissions import (
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
permission_document_comment_edit, permission_document_comment_view
)
from .literals import TEST_COMMENT_TEXT
from .literals import TEST_COMMENT_TEXT, TEST_COMMENT_TEXT_EDITED
from .mixins import DocumentCommentTestMixin
class CommentAPITestCase(
DocumentCommentTestMixin, DocumentTestMixin, BaseAPITestCase
):
def _request_comment_create_view(self):
def _request_test_comment_create_api_view(self):
return self.post(
viewname='rest_api:comment-list', kwargs={
'document_pk': self.test_document.pk
@@ -28,7 +28,7 @@ class CommentAPITestCase(
)
def test_comment_create_view_no_access(self):
response = self._request_comment_create_view()
response = self._request_test_comment_create_api_view()
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(Comment.objects.count(), 0)
@@ -38,14 +38,14 @@ class CommentAPITestCase(
obj=self.test_document, permission=permission_document_comment_create
)
response = self._request_comment_create_view()
response = self._request_test_comment_create_api_view()
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
comment = Comment.objects.first()
self.assertEqual(Comment.objects.count(), 1)
self.assertEqual(response.data['id'], comment.pk)
def _request_comment_delete_view(self):
def _request_test_comment_delete_api_view(self):
return self.delete(
viewname='rest_api:comment-detail', kwargs={
'document_pk': self.test_document.pk,
@@ -56,7 +56,7 @@ class CommentAPITestCase(
def test_comment_delete_view_no_access(self):
self._create_test_comment()
response = self._request_comment_delete_view()
response = self._request_test_comment_delete_api_view()
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertTrue(self.test_document_comment in Comment.objects.all())
@@ -67,12 +67,43 @@ class CommentAPITestCase(
obj=self.test_document, permission=permission_document_comment_delete
)
response = self._request_comment_delete_view()
response = self._request_test_comment_delete_api_view()
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse(self.test_document_comment in Comment.objects.all())
def _request_comment_view(self):
def _request_comment_edit_patch_api_view(self):
return self.patch(
viewname='rest_api:comment-detail', kwargs={
'document_pk': self.test_document.pk,
'comment_pk': self.test_document_comment.pk,
}, data={'comment': TEST_COMMENT_TEXT_EDITED}
)
def test_comment_edit_view_no_access(self):
self._create_test_comment()
comment_text = self.test_document_comment.comment
response = self._request_comment_edit_patch_api_view()
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.test_document_comment.refresh_from_db()
self.assertEqual(self.test_document_comment.comment, comment_text)
def test_comment_edit_view_with_access(self):
self._create_test_comment()
self.grant_access(
obj=self.test_document, permission=permission_document_comment_edit
)
comment_text = self.test_document_comment.comment
response = self._request_comment_edit_patch_api_view()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.test_document_comment.refresh_from_db()
self.assertNotEqual(self.test_document_comment.comment, comment_text)
def _request_test_comment_api_view(self):
return self.get(
viewname='rest_api:comment-detail', kwargs={
'document_pk': self.test_document.pk,
@@ -83,7 +114,7 @@ class CommentAPITestCase(
def test_comment_detail_view_no_access(self):
self._create_test_comment()
response = self._request_comment_view()
response = self._request_test_comment_api_view()
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_comment_detail_view_with_access(self):
@@ -92,12 +123,12 @@ class CommentAPITestCase(
obj=self.test_document, permission=permission_document_comment_view
)
response = self._request_comment_view()
response = self._request_test_comment_api_view()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['comment'], self.test_document_comment.comment)
def _request_comment_list_view(self):
def _request_test_comment_list_api_view(self):
return self.get(
viewname='rest_api:comment-list', kwargs={
'document_pk': self.test_document.pk
@@ -107,7 +138,7 @@ class CommentAPITestCase(
def test_comment_list_view_no_access(self):
self._create_test_comment()
response = self._request_comment_list_view()
response = self._request_test_comment_list_api_view()
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_comment_list_view_with_access(self):
@@ -116,7 +147,7 @@ class CommentAPITestCase(
obj=self.test_document, permission=permission_document_comment_view
)
response = self._request_comment_list_view()
response = self._request_test_comment_list_api_view()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
response.data['results'][0]['comment'], self.test_document_comment.comment

View File

@@ -5,11 +5,13 @@ from actstream.models import Action
from mayan.apps.documents.tests import GenericDocumentViewTestCase
from ..events import (
event_document_comment_created, event_document_comment_deleted
event_document_comment_created, event_document_comment_deleted,
event_document_comment_edited
)
from ..models import Comment
from ..permissions import (
permission_document_comment_create, permission_document_comment_delete
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_edit
)
from .mixins import DocumentCommentTestMixin, DocumentCommentViewTestMixin
@@ -41,9 +43,9 @@ class CommentEventsTestCase(
comment = Comment.objects.first()
self.assertEqual(event.action_object, comment)
self.assertEqual(event.action_object, self.test_document)
self.assertEqual(event.actor, self._test_case_user)
self.assertEqual(event.target, self.test_document)
self.assertEqual(event.target, comment)
self.assertEqual(event.verb, event_document_comment_created.id)
def test_comment_delete_event_no_permissions(self):
@@ -77,3 +79,34 @@ class CommentEventsTestCase(
self.assertEqual(event.actor, self._test_case_user)
self.assertEqual(event.target, self.test_document)
self.assertEqual(event.verb, event_document_comment_deleted.id)
def test_comment_edit_event_no_permissions(self):
self._create_test_comment()
action_count = Action.objects.count()
response = self._request_test_comment_edit_view()
self.assertEqual(response.status_code, 404)
self.assertEqual(Action.objects.count(), action_count)
def test_comment_edit_event_with_access(self):
self._create_test_comment()
self.grant_access(
obj=self.test_document,
permission=permission_document_comment_edit
)
action_count = Action.objects.count()
response = self._request_test_comment_edit_view()
self.assertEqual(response.status_code, 302)
self.assertEqual(Action.objects.count(), action_count + 1)
event = Action.objects.first()
self.assertEqual(event.action_object, self.test_document)
self.assertEqual(event.actor, self._test_case_user)
self.assertEqual(event.target, self.test_document_comment)
self.assertEqual(event.verb, event_document_comment_edited.id)

View File

@@ -0,0 +1,110 @@
from __future__ import unicode_literals
from mayan.apps.documents.tests import GenericDocumentViewTestCase
from ..models import Comment
from ..permissions import (
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_edit, permission_document_comment_view
)
from .mixins import DocumentCommentTestMixin, DocumentCommentViewTestMixin
class DocumentCommentViewTestCase(
DocumentCommentViewTestMixin, DocumentCommentTestMixin,
GenericDocumentViewTestCase
):
def test_comment_create_view_no_permissions(self):
comment_count = Comment.objects.count()
response = self._request_test_comment_create_view()
self.assertEqual(response.status_code, 404)
self.assertEqual(comment_count, Comment.objects.count())
def test_comment_create_view_with_permissions(self):
comment_count = Comment.objects.count()
self.grant_access(
obj=self.test_document,
permission=permission_document_comment_create
)
response = self._request_test_comment_create_view()
self.assertEqual(response.status_code, 302)
self.assertEqual(comment_count + 1, Comment.objects.count())
def test_comment_delete_view_no_permissions(self):
self._create_test_comment()
comment_count = Comment.objects.count()
response = self._request_test_comment_delete_view()
self.assertEqual(response.status_code, 404)
self.assertEqual(Comment.objects.count(), comment_count)
def test_comment_delete_view_with_access(self):
self._create_test_comment()
self.grant_access(
obj=self.test_document,
permission=permission_document_comment_delete
)
comment_count = Comment.objects.count()
response = self._request_test_comment_delete_view()
self.assertEqual(response.status_code, 302)
self.assertEqual(Comment.objects.count(), comment_count - 1)
def test_comment_edit_view_no_permissions(self):
self._create_test_comment()
comment_text = self.test_document_comment.comment
response = self._request_test_comment_edit_view()
self.assertEqual(response.status_code, 404)
self.test_document_comment.refresh_from_db()
self.assertEqual(self.test_document_comment.comment, comment_text)
def test_comment_edit_view_with_access(self):
self._create_test_comment()
self.grant_access(
obj=self.test_document, permission=permission_document_comment_edit
)
comment_text = self.test_document_comment.comment
response = self._request_test_comment_edit_view()
self.assertEqual(response.status_code, 302)
self.test_document_comment.refresh_from_db()
self.assertNotEqual(self.test_document_comment.comment, comment_text)
def test_comment_list_view_with_no_permission(self):
self._create_test_comment()
response = self._request_test_comment_list_view()
self.assertNotContains(
response=response, text=self.test_document_comment.comment,
status_code=404
)
def test_comment_list_view_with_access(self):
self._create_test_comment()
self.grant_access(
obj=self.test_document,
permission=permission_document_comment_view
)
response = self._request_test_comment_list_view()
self.assertContains(
response=response, text=self.test_document_comment.comment,
status_code=200
)

View File

@@ -5,17 +5,26 @@ from django.conf.urls import url
from .api_views import APICommentListView, APICommentView
from .views import (
DocumentCommentCreateView, DocumentCommentDeleteView,
DocumentCommentDetailView, DocumentCommentEditView,
DocumentCommentListView
)
urlpatterns = [
url(
regex=r'^(?P<pk>\d+)/comment/add/$',
view=DocumentCommentCreateView.as_view(), name='comment_add'
),
url(
regex=r'^comment/(?P<pk>\d+)/delete/$',
view=DocumentCommentDeleteView.as_view(), name='comment_delete'
),
url(
regex=r'^(?P<pk>\d+)/comment/add/$',
view=DocumentCommentCreateView.as_view(), name='comment_add'
regex=r'^comment/(?P<pk>\d+)/$',
view=DocumentCommentDetailView.as_view(), name='comment_details'
),
url(
regex=r'^comment/(?P<pk>\d+)/edit/$',
view=DocumentCommentEditView.as_view(), name='comment_edit'
),
url(
regex=r'^(?P<pk>\d+)/comment/list/$',

View File

@@ -5,17 +5,19 @@ from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from mayan.apps.common.generics import (
SingleObjectCreateView, SingleObjectDeleteView, SingleObjectListView
SingleObjectCreateView, SingleObjectDeleteView, SingleObjectDetailView,
SingleObjectEditView, SingleObjectListView
)
from mayan.apps.common.mixins import ExternalObjectMixin
from mayan.apps.documents.models import Document
from .forms import DocumentCommentDetailForm
from .icons import icon_comments_for_document
from .links import link_comment_add
from .models import Comment
from .permissions import (
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
permission_document_comment_edit, permission_document_comment_view
)
@@ -24,7 +26,6 @@ class DocumentCommentCreateView(ExternalObjectMixin, SingleObjectCreateView):
external_object_permission = permission_document_comment_create
external_object_pk_url_kwarg = 'pk'
fields = ('comment',)
model = Comment
def get_extra_context(self):
return {
@@ -44,6 +45,9 @@ class DocumentCommentCreateView(ExternalObjectMixin, SingleObjectCreateView):
}
)
def get_queryset(self):
return self.external_object.comments.all()
def get_save_extra_data(self):
return {
'_user': self.request.user,
@@ -60,7 +64,9 @@ class DocumentCommentDeleteView(SingleObjectDeleteView):
def get_extra_context(self):
return {
'object': self.object.document,
'comment': self.object,
'document': self.object.document,
'navigation_object_list': ('document', 'comment'),
'title': _('Delete comment: %s?') % self.object,
}
@@ -72,6 +78,46 @@ class DocumentCommentDeleteView(SingleObjectDeleteView):
)
class DocumentCommentDetailView(SingleObjectDetailView):
form_class = DocumentCommentDetailForm
model = Comment
pk_url_kwarg = 'pk'
object_permission = permission_document_comment_view
def get_extra_context(self):
return {
'comment': self.object,
'document': self.object.document,
'navigation_object_list': ('document', 'comment'),
'title': _('Details for comment: %s?') % self.object,
}
class DocumentCommentEditView(SingleObjectEditView):
fields = ('comment',)
model = Comment
pk_url_kwarg = 'pk'
object_permission = permission_document_comment_edit
def get_save_extra_data(self):
return {'_user': self.request.user}
def get_extra_context(self):
return {
'comment': self.object,
'document': self.object.document,
'navigation_object_list': ('document', 'comment'),
'title': _('Edit comment: %s?') % self.object,
}
def get_post_action_redirect(self):
return reverse(
viewname='comments:comments_for_document', kwargs={
'pk': self.object.document.pk
}
)
class DocumentCommentListView(ExternalObjectMixin, SingleObjectListView):
external_object_class = Document
external_object_permission = permission_document_comment_view