From 395c36078445f7cf548b09d3f021416b8b2a6056 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Sun, 28 Apr 2019 01:44:23 -0400 Subject: [PATCH] Update smart link document type view use AddRemove Add smart link created and edited events. Signed-off-by: Roberto Rosario --- HISTORY.rst | 5 +- docs/releases/3.2.rst | 3 + mayan/apps/linking/apps.py | 18 +++++- mayan/apps/linking/events.py | 16 +++++ mayan/apps/linking/models.py | 41 ++++++++++++- mayan/apps/linking/tests/mixins.py | 34 +++++++++- mayan/apps/linking/tests/test_events.py | 48 +++++++++++++++ mayan/apps/linking/tests/test_views.py | 36 +++-------- mayan/apps/linking/views.py | 82 ++++++++++++------------- 9 files changed, 206 insertions(+), 77 deletions(-) create mode 100644 mayan/apps/linking/events.py create mode 100644 mayan/apps/linking/tests/test_events.py diff --git a/HISTORY.rst b/HISTORY.rst index e1d779a966..16541b7c34 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -194,7 +194,10 @@ from the AddRemoveView. * Add view to link document type and indexes from the document type side. - +* Update smart link document type selection view to use + AddRemoveView class. +* Add smart link created and edited events. + 3.1.11 (2019-04-XX) =================== * Fix multiple tag selection wizard step. diff --git a/docs/releases/3.2.rst b/docs/releases/3.2.rst index 717555d780..8476eff8a6 100644 --- a/docs/releases/3.2.rst +++ b/docs/releases/3.2.rst @@ -226,6 +226,9 @@ Other changes from the AddRemoveView. * Add view to link document type and indexes from the document type side. +* Update smart link document type selection view to use + AddRemoveView class. +* Add smart link created and edited events. Removals -------- diff --git a/mayan/apps/linking/apps.py b/mayan/apps/linking/apps.py index d161be39ae..5ea812f561 100644 --- a/mayan/apps/linking/apps.py +++ b/mayan/apps/linking/apps.py @@ -11,8 +11,13 @@ from mayan.apps.common.html_widgets import TwoStateWidget from mayan.apps.common.menus import ( menu_facet, menu_list_facet, menu_object, menu_secondary, menu_setup ) +from mayan.apps.events.classes import ModelEventType +from mayan.apps.events.links import ( + link_events_for_object, link_object_event_types_user_subcriptions_list +) from mayan.apps.navigation.classes import SourceColumn +from .events import event_smart_link_created, event_smart_link_edited from .links import ( link_smart_link_create, link_smart_link_condition_create, link_smart_link_condition_delete, link_smart_link_condition_edit, @@ -37,6 +42,7 @@ class LinkingApp(MayanAppConfig): def ready(self): super(LinkingApp, self).ready() + from actstream import registry Document = apps.get_model( app_label='documents', model_name='Document' @@ -46,6 +52,12 @@ class LinkingApp(MayanAppConfig): SmartLink = self.get_model(model_name='SmartLink') SmartLinkCondition = self.get_model(model_name='SmartLinkCondition') + ModelEventType.register( + event_types=( + event_smart_link_created, event_smart_link_edited + ), model=SmartLink + ) + ModelPermission.register( model=SmartLink, permissions=( permission_acl_edit, permission_acl_view, @@ -79,7 +91,9 @@ class LinkingApp(MayanAppConfig): ) menu_list_facet.bind_links( links=( - link_acl_list, link_smart_link_document_types, + link_acl_list, link_events_for_object, + link_smart_link_document_types, + link_object_event_types_user_subcriptions_list, link_smart_link_condition_list, ), sources=(SmartLink,) ) @@ -115,3 +129,5 @@ class LinkingApp(MayanAppConfig): ) ) menu_setup.bind_links(links=(link_smart_link_setup,)) + + registry.register(SmartLink) diff --git a/mayan/apps/linking/events.py b/mayan/apps/linking/events.py new file mode 100644 index 0000000000..263466f0ca --- /dev/null +++ b/mayan/apps/linking/events.py @@ -0,0 +1,16 @@ +from __future__ import absolute_import, unicode_literals + +from django.utils.translation import ugettext_lazy as _ + +from mayan.apps.events.classes import EventTypeNamespace + +namespace = EventTypeNamespace( + label=_('Smart links'), name='linking' +) + +event_smart_link_created = namespace.add_event_type( + label=_('Smart link created'), name='smart_link_created' +) +event_smart_link_edited = namespace.add_event_type( + label=_('Smart link edited'), name='smart_link_edited' +) diff --git a/mayan/apps/linking/models.py b/mayan/apps/linking/models.py index cf639ac7ce..a50cd24528 100644 --- a/mayan/apps/linking/models.py +++ b/mayan/apps/linking/models.py @@ -1,13 +1,15 @@ from __future__ import unicode_literals -from django.db import models +from django.db import models, transaction from django.db.models import Q from django.template import Context, Template from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ +from mayan.apps.documents.events import event_document_type_edited from mayan.apps.documents.models import Document, DocumentType +from .events import event_smart_link_created, event_smart_link_edited from .literals import ( INCLUSION_AND, INCLUSION_CHOICES, INCLUSION_OR, OPERATOR_CHOICES ) @@ -47,6 +49,28 @@ class SmartLink(models.Model): def __str__(self): return self.label + def document_types_add(self, queryset, _user=None): + with transaction.atomic(): + event_smart_link_edited.commit( + actor=_user, target=self + ) + for obj in queryset: + self.document_types.add(obj) + event_document_type_edited.commit( + actor=_user, action_object=self, target=obj + ) + + def document_types_remove(self, queryset, _user=None): + with transaction.atomic(): + event_smart_link_edited.commit( + actor=_user, target=self + ) + for obj in queryset: + self.document_types.remove(obj) + event_document_type_edited.commit( + actor=_user, action_object=self, target=obj + ) + def get_dynamic_label(self, document): """ If the smart links was created using a template label instead of a @@ -111,6 +135,21 @@ class SmartLink(models.Model): ) ) + def save(self, *args, **kwargs): + _user = kwargs.pop('_user', None) + + with transaction.atomic(): + is_new = not self.pk + super(SmartLink, self).save(*args, **kwargs) + if is_new: + event_smart_link_created.commit( + actor=_user, target=self + ) + else: + event_smart_link_edited.commit( + actor=_user, target=self + ) + class ResolvedSmartLink(SmartLink): """ diff --git a/mayan/apps/linking/tests/mixins.py b/mayan/apps/linking/tests/mixins.py index 700d051f31..5d482d6731 100644 --- a/mayan/apps/linking/tests/mixins.py +++ b/mayan/apps/linking/tests/mixins.py @@ -6,7 +6,7 @@ from .literals import ( TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA, TEST_SMART_LINK_CONDITION_EXPRESSION, TEST_SMART_LINK_CONDITION_OPERATOR, TEST_SMART_LINK_DYNAMIC_LABEL, - TEST_SMART_LINK_LABEL + TEST_SMART_LINK_LABEL, TEST_SMART_LINK_LABEL_EDITED ) @@ -26,3 +26,35 @@ class SmartLinkTestMixin(object): expression=TEST_SMART_LINK_CONDITION_EXPRESSION, operator=TEST_SMART_LINK_CONDITION_OPERATOR ) + + +class SmartLinkViewTestMixin(object): + def _request_test_smart_link_create_view(self): + # Typecast to list to force queryset evaluation + values = list(SmartLink.objects.values_list('pk', flat=True)) + + response = self.post( + viewname='linking:smart_link_create', data={ + 'label': TEST_SMART_LINK_LABEL + } + ) + + self.test_smart_link = SmartLink.objects.exclude(pk__in=values).first() + + return response + + def _request_test_smart_link_delete_view(self): + return self.post( + viewname='linking:smart_link_delete', kwargs={ + 'pk': self.test_smart_link.pk + } + ) + + def _request_test_smart_link_edit_view(self): + return self.post( + viewname='linking:smart_link_edit', kwargs={ + 'pk': self.test_smart_link.pk + }, data={ + 'label': TEST_SMART_LINK_LABEL_EDITED + } + ) diff --git a/mayan/apps/linking/tests/test_events.py b/mayan/apps/linking/tests/test_events.py new file mode 100644 index 0000000000..7ffed7dd96 --- /dev/null +++ b/mayan/apps/linking/tests/test_events.py @@ -0,0 +1,48 @@ +from __future__ import unicode_literals + +from actstream.models import Action + +from mayan.apps.common.tests import GenericViewTestCase +from mayan.apps.documents.tests import DocumentTestMixin + +from ..permissions import ( + permission_smart_link_create, permission_smart_link_edit, +) + +from ..events import event_smart_link_created, event_smart_link_edited + +from .mixins import SmartLinkTestMixin, SmartLinkViewTestMixin + + +class SmartLinkTemplateEventsTestCase(DocumentTestMixin, SmartLinkTestMixin, SmartLinkViewTestMixin, GenericViewTestCase): + auto_upload_document = False + + def test_smart_link_create_event(self): + self.grant_permission( + permission=permission_smart_link_create + ) + Action.objects.all().delete() + + response = self._request_test_smart_link_create_view() + self.assertEqual(response.status_code, 302) + + action = Action.objects.last() + + self.assertEqual(action.actor, self._test_case_user) + self.assertEqual(action.target, self.test_smart_link) + self.assertEqual(action.verb, event_smart_link_created.id) + + def test_smart_link_edit_event(self): + self._create_test_smart_link() + + self.grant_permission(permission=permission_smart_link_edit) + Action.objects.all().delete() + + response = self._request_test_smart_link_edit_view() + self.assertEqual(response.status_code, 302) + + action = Action.objects.last() + + self.assertEqual(action.actor, self._test_case_user) + self.assertEqual(action.target, self.test_smart_link) + self.assertEqual(action.verb, event_smart_link_edited.id) diff --git a/mayan/apps/linking/tests/test_views.py b/mayan/apps/linking/tests/test_views.py index ab06c5bf6f..bf00d1eb1f 100644 --- a/mayan/apps/linking/tests/test_views.py +++ b/mayan/apps/linking/tests/test_views.py @@ -14,17 +14,10 @@ from .literals import ( TEST_SMART_LINK_DYNAMIC_LABEL, TEST_SMART_LINK_LABEL_EDITED, TEST_SMART_LINK_LABEL ) -from .mixins import SmartLinkTestMixin +from .mixins import SmartLinkTestMixin, SmartLinkViewTestMixin -class SmartLinkViewTestCase(SmartLinkTestMixin, GenericViewTestCase): - def _request_test_smart_link_create_view(self): - return self.post( - viewname='linking:smart_link_create', data={ - 'label': TEST_SMART_LINK_LABEL - } - ) - +class SmartLinkViewTestCase(SmartLinkTestMixin, SmartLinkViewTestMixin, GenericViewTestCase): def test_smart_link_create_view_no_permission(self): response = self._request_test_smart_link_create_view() self.assertEquals(response.status_code, 403) @@ -42,13 +35,6 @@ class SmartLinkViewTestCase(SmartLinkTestMixin, GenericViewTestCase): SmartLink.objects.first().label, TEST_SMART_LINK_LABEL ) - def _request_test_smart_link_delete_view(self): - return self.post( - viewname='linking:smart_link_delete', kwargs={ - 'pk': self.test_smart_link.pk - } - ) - def test_smart_link_delete_view_no_permission(self): self._create_test_smart_link() @@ -66,15 +52,6 @@ class SmartLinkViewTestCase(SmartLinkTestMixin, GenericViewTestCase): self.assertEqual(SmartLink.objects.count(), 0) - def _request_test_smart_link_edit_view(self): - return self.post( - viewname='linking:smart_link_edit', kwargs={ - 'pk': self.test_smart_link.pk - }, data={ - 'label': TEST_SMART_LINK_LABEL_EDITED - } - ) - def test_smart_link_edit_view_no_permission(self): self._create_test_smart_link() @@ -124,10 +101,11 @@ class SmartLinkDocumentViewTestCase(SmartLinkTestMixin, GenericDocumentViewTestC ) response = self._request_test_smart_link_document_instances_view() - # Text must appear 2 times, only for the windows title and template + # Text must appear 3 times, two for the title and one for the template # heading. The two smart links are not shown. self.assertContains( - response, text=self.test_document.label, count=2, status_code=200 + response=response, text=self.test_document.label, count=3, + status_code=200 ) def test_document_smart_link_list_view_with_permission(self): @@ -144,8 +122,8 @@ class SmartLinkDocumentViewTestCase(SmartLinkTestMixin, GenericDocumentViewTestC ) response = self._request_test_smart_link_document_instances_view() - # Text must appear 4 times: 2 for the windows title and template + # Text must appear 5 times: 3 for the windows title and template # heading, plus 2 for the test. self.assertContains( - response, text=self.test_document.label, count=4, status_code=200 + response, text=self.test_document.label, count=5, status_code=200 ) diff --git a/mayan/apps/linking/views.py b/mayan/apps/linking/views.py index c4b5e4c5f0..0e32467a68 100644 --- a/mayan/apps/linking/views.py +++ b/mayan/apps/linking/views.py @@ -11,11 +11,13 @@ from django.utils.translation import ugettext_lazy as _ from mayan.apps.acls.models import AccessControlList from mayan.apps.common.generics import ( - AssignRemoveView, SingleObjectCreateView, SingleObjectDeleteView, + AddRemoveView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView ) from mayan.apps.documents.models import Document, DocumentType -from mayan.apps.documents.permissions import permission_document_view +from mayan.apps.documents.permissions import ( + permission_document_type_edit, permission_document_view +) from mayan.apps.documents.views import DocumentListView from .forms import SmartLinkConditionForm, SmartLinkForm @@ -97,43 +99,29 @@ class ResolvedSmartLinkView(DocumentListView): return context -class SetupSmartLinkDocumentTypesView(AssignRemoveView): - decode_content_type = True - left_list_title = _('Available document types') - object_permission = permission_smart_link_edit - right_list_title = _('Document types enabled') +class SetupSmartLinkDocumentTypesView(AddRemoveView): + main_object_method_add = 'document_types_add' + main_object_method_remove = 'document_types_remove' + main_object_permission = permission_smart_link_edit + main_object_model = SmartLink + main_object_pk_url_kwarg = 'pk' + secondary_object_model = DocumentType + secondary_object_permission = permission_document_type_edit + list_available_title = _('Available document types') + list_added_title = _('Document types enabled') + related_field = 'document_types' - def add(self, item): - self.get_object().document_types.add(item) + def get_actions_extra_kwargs(self): + return {'_user': self.request.user} def get_extra_context(self): return { - 'object': self.get_object(), + 'object': self.main_object, 'title': _( 'Document type for which to enable smart link: %s' - ) % self.get_object() + ) % self.main_object, } - def get_object(self): - return get_object_or_404(klass=SmartLink, pk=self.kwargs['pk']) - - def left_list(self): - # TODO: filter document type list by user ACL - return AssignRemoveView.generate_choices( - DocumentType.objects.exclude( - pk__in=self.get_object().document_types.all() - ) - ) - - def remove(self, item): - self.get_object().document_types.remove(item) - - def right_list(self): - # TODO: filter document type list by user ACL - return AssignRemoveView.generate_choices( - self.get_object().document_types.all() - ) - class SmartLinkListView(SingleObjectListView): object_permission = permission_smart_link_view @@ -208,6 +196,23 @@ class SmartLinkCreateView(SingleObjectCreateView): ) view_permission = permission_smart_link_create + def get_save_extra_data(self): + return {'_user': self.request.user} + + +class SmartLinkDeleteView(SingleObjectDeleteView): + model = SmartLink + post_action_redirect = reverse_lazy( + viewname='linking:smart_link_list' + ) + view_permission = permission_smart_link_delete + + def get_extra_context(self): + return { + 'object': self.get_object(), + 'title': _('Delete smart link: %s') % self.get_object() + } + class SmartLinkEditView(SingleObjectEditView): form_class = SmartLinkForm @@ -223,19 +228,8 @@ class SmartLinkEditView(SingleObjectEditView): 'title': _('Edit smart link: %s') % self.get_object() } - -class SmartLinkDeleteView(SingleObjectDeleteView): - model = SmartLink - post_action_redirect = reverse_lazy( - viewname='linking:smart_link_list' - ) - view_permission = permission_smart_link_delete - - def get_extra_context(self): - return { - 'object': self.get_object(), - 'title': _('Delete smart link: %s') % self.get_object() - } + def get_save_extra_data(self): + return {'_user': self.request.user} class SmartLinkConditionListView(SingleObjectListView):