Update comments app

Add transaction handling. Add comment view link. Update views to
user ExternalObjectMixin. Add event tests.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2019-05-09 20:22:12 -04:00
parent fffcf4d3da
commit cbd51c5f26
10 changed files with 216 additions and 130 deletions

View File

@@ -8,8 +8,8 @@ from mayan.apps.acls.models import AccessControlList
from mayan.apps.documents.models import Document
from .permissions import (
permission_comment_create, permission_comment_delete,
permission_comment_view
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
)
from .serializers import CommentSerializer, WritableCommentSerializer
@@ -21,9 +21,9 @@ class APICommentListView(generics.ListCreateAPIView):
"""
def get_document(self):
if self.request.method == 'GET':
permission_required = permission_comment_view
permission_required = permission_document_comment_view
else:
permission_required = permission_comment_create
permission_required = permission_document_comment_create
document = get_object_or_404(
klass=Document, pk=self.kwargs['document_pk']
@@ -76,9 +76,9 @@ class APICommentView(generics.RetrieveDestroyAPIView):
def get_document(self):
if self.request.method == 'GET':
permission_required = permission_comment_view
permission_required = permission_document_comment_view
else:
permission_required = permission_comment_delete
permission_required = permission_document_comment_delete
document = get_object_or_404(
klass=Document, pk=self.kwargs['document_pk']

View File

@@ -5,20 +5,26 @@ from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.classes import ModelPermission
from mayan.apps.common.apps import MayanAppConfig
from mayan.apps.common.menus import menu_facet, menu_object, menu_secondary
from mayan.apps.common.menus import (
menu_facet, menu_list_facet, menu_object, menu_secondary
)
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,
)
from mayan.apps.events.permissions import permission_events_view
from mayan.apps.navigation.classes import SourceColumn
from .events import (
event_document_comment_create, event_document_comment_delete
event_document_comment_created, event_document_comment_deleted
)
from .links import (
link_comment_add, link_comment_delete, link_comments_for_document
)
from .permissions import (
permission_comment_create, permission_comment_delete,
permission_comment_view
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
)
@@ -32,6 +38,7 @@ class DocumentCommentsApp(MayanAppConfig):
def ready(self):
super(DocumentCommentsApp, self).ready()
from actstream import registry
Document = apps.get_model(
app_label='documents', model_name='Document'
@@ -41,14 +48,20 @@ class DocumentCommentsApp(MayanAppConfig):
ModelEventType.register(
model=Document, event_types=(
event_document_comment_create, event_document_comment_delete
event_document_comment_created, event_document_comment_deleted
)
)
ModelPermission.register(
model=Comment, permissions=(permission_events_view,)
)
ModelPermission.register_inheritance(
model=Comment, related='document',
)
ModelPermission.register(
model=Document, permissions=(
permission_comment_create, permission_comment_delete,
permission_comment_view
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
)
)
@@ -68,6 +81,16 @@ class DocumentCommentsApp(MayanAppConfig):
label=_('Comments')
)
menu_facet.bind_links(
links=(link_comments_for_document,), sources=(Document,)
)
menu_list_facet.bind_links(
links=(
link_events_for_object,
), sources=(Comment,)
)
menu_secondary.bind_links(
links=(link_comment_add,),
sources=(
@@ -75,9 +98,9 @@ class DocumentCommentsApp(MayanAppConfig):
'comments:comment_delete', 'comments:comment_multiple_delete'
)
)
menu_object.bind_links(
links=(link_comment_delete,), sources=(Comment,)
)
menu_facet.bind_links(
links=(link_comments_for_document,), sources=(Document,)
)
registry.register(Comment)

View File

@@ -8,9 +8,9 @@ namespace = EventTypeNamespace(
label=_('Document comments'), name='document_comments'
)
event_document_comment_create = namespace.add_event_type(
event_document_comment_created = namespace.add_event_type(
label=_('Document comment created'), name='create'
)
event_document_comment_delete = namespace.add_event_type(
event_document_comment_deleted = namespace.add_event_type(
label=_('Document comment deleted'), name='delete'
)

View File

@@ -6,22 +6,22 @@ from mayan.apps.navigation.classes import Link
from .icons import icon_comment_add, icon_comment_delete, icon_comments_for_document
from .permissions import (
permission_comment_create, permission_comment_delete,
permission_comment_view
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
)
link_comment_add = Link(
args='object.pk', icon_class=icon_comment_add,
permissions=(permission_comment_create,), text=_('Add comment'),
permissions=(permission_document_comment_create,), text=_('Add comment'),
view='comments:comment_add',
)
link_comment_delete = Link(
args='object.pk', icon_class=icon_comment_delete,
permissions=(permission_comment_delete,), tags='dangerous',
permissions=(permission_document_comment_delete,), tags='dangerous',
text=_('Delete'), view='comments:comment_delete',
)
link_comments_for_document = Link(
args='resolved_object.pk', icon_class=icon_comments_for_document,
permissions=(permission_comment_view,), text=_('Comments'),
permissions=(permission_document_comment_view,), text=_('Comments'),
view='comments:comments_for_document',
)

View File

@@ -3,14 +3,14 @@ from __future__ import unicode_literals
import logging
from django.conf import settings
from django.db import models
from django.db import models, transaction
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_create, event_document_comment_delete
event_document_comment_created, event_document_comment_deleted
)
logger = logging.getLogger(__name__)
@@ -46,31 +46,20 @@ class Comment(models.Model):
return self.comment
def delete(self, *args, **kwargs):
user = kwargs.pop('_user', None)
_user = kwargs.pop('_user', None)
with transaction.atomic():
super(Comment, self).delete(*args, **kwargs)
if user:
event_document_comment_delete.commit(
actor=user, target=self.document
event_document_comment_deleted.commit(
actor=_user, target=self.document
)
else:
event_document_comment_delete.commit(target=self.document)
def save(self, *args, **kwargs):
user = kwargs.pop('_user', None) or self.user
is_new = not self.pk
_user = kwargs.pop('_user', None) or self.user
created = not self.pk
with transaction.atomic():
super(Comment, self).save(*args, **kwargs)
if is_new:
if user:
event_document_comment_create.commit(
actor=user, target=self.document
)
logger.info(
'Comment "%s" added to document "%s" by user "%s"',
self.comment, self.document, user
)
else:
event_document_comment_create.commit(target=self.document)
logger.info(
'Comment "%s" added to document "%s"', self.comment,
self.document
if created:
event_document_comment_created.commit(
action_object=self, actor=_user, target=self.document,
)

View File

@@ -6,12 +6,12 @@ from mayan.apps.permissions import PermissionNamespace
namespace = PermissionNamespace(label=_('Comments'), name='comments')
permission_comment_create = namespace.add_permission(
permission_document_comment_create = namespace.add_permission(
label=_('Create new comments'), name='comment_create'
)
permission_comment_delete = namespace.add_permission(
permission_document_comment_delete = namespace.add_permission(
label=_('Delete comments'), name='comment_delete'
)
permission_comment_view = namespace.add_permission(
permission_document_comment_view = namespace.add_permission(
label=_('View comments'), name='comment_view'
)

View File

@@ -0,0 +1,26 @@
from __future__ import unicode_literals
from .literals import TEST_COMMENT_TEXT
class DocumentCommentTestMixin(object):
def _create_test_comment(self):
self.test_document_comment = self.test_document.comments.create(
comment=TEST_COMMENT_TEXT, user=self._test_case_user
)
class DocumentCommentViewTestMixin(object):
def _request_test_comment_create_view(self):
return self.post(
viewname='comments:comment_add', kwargs={
'pk': self.test_document.pk
}, data={'comment': TEST_COMMENT_TEXT}
)
def _request_test_comment_delete_view(self):
return self.post(
viewname='comments:comment_delete', kwargs={
'pk': self.test_document_comment.pk
},
)

View File

@@ -7,19 +7,17 @@ from mayan.apps.rest_api.tests import BaseAPITestCase
from ..models import Comment
from ..permissions import (
permission_comment_create, permission_comment_delete,
permission_comment_view
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
)
from .literals import TEST_COMMENT_TEXT
from .mixins import DocumentCommentTestMixin
class CommentAPITestCase(DocumentTestMixin, BaseAPITestCase):
def _create_test_comment(self):
return self.test_document.comments.create(
comment=TEST_COMMENT_TEXT, user=self._test_case_user
)
class CommentAPITestCase(
DocumentCommentTestMixin, DocumentTestMixin, BaseAPITestCase
):
def _request_comment_create_view(self):
return self.post(
viewname='rest_api:comment-list', kwargs={
@@ -37,7 +35,7 @@ class CommentAPITestCase(DocumentTestMixin, BaseAPITestCase):
def test_comment_create_view_with_access(self):
self.grant_access(
obj=self.test_document, permission=permission_comment_create
obj=self.test_document, permission=permission_document_comment_create
)
response = self._request_comment_create_view()
@@ -51,53 +49,53 @@ class CommentAPITestCase(DocumentTestMixin, BaseAPITestCase):
return self.delete(
viewname='rest_api:comment-detail', kwargs={
'document_pk': self.test_document.pk,
'comment_pk': self.test_comment.pk,
'comment_pk': self.test_document_comment.pk,
}
)
def test_comment_delete_view_no_access(self):
self.test_comment = self._create_test_comment()
self._create_test_comment()
response = self._request_comment_delete_view()
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertTrue(self.test_comment in Comment.objects.all())
self.assertTrue(self.test_document_comment in Comment.objects.all())
def test_comment_delete_view_with_access(self):
self.test_comment = self._create_test_comment()
self._create_test_comment()
self.grant_access(
obj=self.test_document, permission=permission_comment_delete
obj=self.test_document, permission=permission_document_comment_delete
)
response = self._request_comment_delete_view()
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse(self.test_comment in Comment.objects.all())
self.assertFalse(self.test_document_comment in Comment.objects.all())
def _request_comment_view(self):
return self.get(
viewname='rest_api:comment-detail', kwargs={
'document_pk': self.test_document.pk,
'comment_pk': self.test_comment.pk
'comment_pk': self.test_document_comment.pk
}
)
def test_comment_detail_view_no_access(self):
self.test_comment = self._create_test_comment()
self._create_test_comment()
response = self._request_comment_view()
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_comment_detail_view_with_access(self):
self.test_comment = self._create_test_comment()
self._create_test_comment()
self.grant_access(
obj=self.test_document, permission=permission_comment_view
obj=self.test_document, permission=permission_document_comment_view
)
response = self._request_comment_view()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['comment'], self.test_comment.comment)
self.assertEqual(response.data['comment'], self.test_document_comment.comment)
def _request_comment_list_view(self):
return self.get(
@@ -107,19 +105,19 @@ class CommentAPITestCase(DocumentTestMixin, BaseAPITestCase):
)
def test_comment_list_view_no_access(self):
self.test_comment = self._create_test_comment()
self._create_test_comment()
response = self._request_comment_list_view()
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_comment_list_view_with_access(self):
self.test_comment = self._create_test_comment()
self._create_test_comment()
self.grant_access(
obj=self.test_document, permission=permission_comment_view
obj=self.test_document, permission=permission_document_comment_view
)
response = self._request_comment_list_view()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
response.data['results'][0]['comment'], self.test_comment.comment
response.data['results'][0]['comment'], self.test_document_comment.comment
)

View File

@@ -0,0 +1,79 @@
from __future__ import unicode_literals
from actstream.models import Action
from mayan.apps.documents.tests import GenericDocumentViewTestCase
from ..events import (
event_document_comment_created, event_document_comment_deleted
)
from ..models import Comment
from ..permissions import (
permission_document_comment_create, permission_document_comment_delete
)
from .mixins import DocumentCommentTestMixin, DocumentCommentViewTestMixin
class CommentEventsTestCase(
DocumentCommentTestMixin, DocumentCommentViewTestMixin,
GenericDocumentViewTestCase
):
def test_comment_create_event_no_permissions(self):
action_count = Action.objects.count()
response = self._request_test_comment_create_view()
self.assertEqual(response.status_code, 404)
self.assertEqual(Action.objects.count(), action_count)
def test_comment_create_event_with_permissions(self):
self.grant_permission(permission=permission_document_comment_create)
action_count = Action.objects.count()
response = self._request_test_comment_create_view()
self.assertEqual(response.status_code, 302)
self.assertEqual(Action.objects.count(), action_count + 1)
event = Action.objects.first()
comment = Comment.objects.first()
self.assertEqual(event.action_object, comment)
self.assertEqual(event.actor, self._test_case_user)
self.assertEqual(event.target, self.test_document)
self.assertEqual(event.verb, event_document_comment_created.id)
def test_comment_delete_event_no_permissions(self):
self._create_test_comment()
action_count = Action.objects.count()
response = self._request_test_comment_delete_view()
self.assertEqual(response.status_code, 404)
self.assertEqual(Action.objects.count(), action_count)
def test_comment_delete_event_with_access(self):
self._create_test_comment()
self.grant_access(
obj=self.test_document,
permission=permission_document_comment_delete
)
action_count = Action.objects.count()
response = self._request_test_comment_delete_view()
self.assertEqual(response.status_code, 302)
# Total count remains the same. Document comment created is removed due
# to cascade delete, document comment deleted event is added.
self.assertEqual(Action.objects.count(), action_count)
event = Action.objects.first()
self.assertEqual(event.actor, self._test_case_user)
self.assertEqual(event.target, self.test_document)
self.assertEqual(event.verb, event_document_comment_deleted.id)

View File

@@ -1,51 +1,40 @@
from __future__ import absolute_import, unicode_literals
from django.shortcuts import get_object_or_404
from django.template import RequestContext
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.generics import (
SingleObjectCreateView, SingleObjectDeleteView, SingleObjectListView
)
from mayan.apps.common.mixins import ExternalObjectMixin
from mayan.apps.documents.models import Document
from .icons import icon_comments_for_document
from .links import link_comment_add
from .models import Comment
from .permissions import (
permission_comment_create, permission_comment_delete,
permission_comment_view
permission_document_comment_create, permission_document_comment_delete,
permission_document_comment_view
)
class DocumentCommentCreateView(SingleObjectCreateView):
class DocumentCommentCreateView(ExternalObjectMixin, SingleObjectCreateView):
external_object_class = Document
external_object_permission = permission_document_comment_create
external_object_pk_url_kwarg = 'pk'
fields = ('comment',)
model = Comment
def dispatch(self, request, *args, **kwargs):
AccessControlList.objects.check_access(
obj=self.get_document(), permissions=(permission_comment_create,),
user=request.user
)
return super(
DocumentCommentCreateView, self
).dispatch(request, *args, **kwargs)
def get_document(self):
return get_object_or_404(klass=Document, pk=self.kwargs['pk'])
def get_extra_context(self):
return {
'object': self.get_document(),
'title': _('Add comment to document: %s') % self.get_document(),
'object': self.external_object,
'title': _('Add comment to document: %s') % self.external_object,
}
def get_instance_extra_data(self):
return {
'document': self.get_document(), 'user': self.request.user,
'document': self.external_object, 'user': self.request.user,
}
def get_post_action_redirect(self):
@@ -63,65 +52,47 @@ class DocumentCommentCreateView(SingleObjectCreateView):
class DocumentCommentDeleteView(SingleObjectDeleteView):
model = Comment
def dispatch(self, request, *args, **kwargs):
AccessControlList.objects.check_access(
obj=self.get_object().document,
permissions=(permission_comment_delete,), user=request.user
)
return super(
DocumentCommentDeleteView, self
).dispatch(request, *args, **kwargs)
pk_url_kwarg = 'pk'
object_permission = permission_document_comment_delete
def get_delete_extra_data(self):
return {'_user': self.request.user}
def get_extra_context(self):
return {
'comment': self.get_object(),
'navigation_object_list': ('object', 'comment'),
'object': self.get_object().document,
'title': _('Delete comment: %s?') % self.get_object(),
'object': self.object.document,
'title': _('Delete comment: %s?') % self.object,
}
def get_post_action_redirect(self):
return reverse(
viewname='comments:comments_for_document', kwargs={
'pk': self.get_object().document.pk
'pk': self.object.document.pk
}
)
class DocumentCommentListView(SingleObjectListView):
def get_document(self):
return get_object_or_404(klass=Document, pk=self.kwargs['pk'])
class DocumentCommentListView(ExternalObjectMixin, SingleObjectListView):
external_object_class = Document
external_object_permission = permission_document_comment_view
external_object_pk_url_kwarg = 'pk'
def get_extra_context(self):
return {
'hide_link': True,
'hide_object': True,
'no_results_icon': icon_comments_for_document,
'no_results_external_link': link_comment_add.resolve(
RequestContext(self.request, {'object': self.external_object})
),
'no_results_text': _(
'Document comments are timestamped text entries from users. '
'They are great for collaboration.'
),
'no_results_main_link': link_comment_add.resolve(
RequestContext(
request=self.request, dict_={
'object': self.get_document()
}
)
),
'no_results_title': _('There are no comments'),
'object': self.get_document(),
'title': _('Comments for document: %s') % self.get_document(),
'object': self.external_object,
'title': _('Comments for document: %s') % self.external_object,
}
def get_source_queryset(self):
AccessControlList.objects.check_access(
obj=self.get_document(), permissions=(permission_comment_view,),
user=self.request.user
)
return self.get_document().comments.all()
return self.external_object.comments.all()