diff --git a/HISTORY.rst b/HISTORY.rst index 76b9a5fc36..a099a19dde 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -223,6 +223,9 @@ * Remove mayan_permission_attribute_check from API permission. * Update Bootstrap and Bootswatch to version 3.4.1. +* Convert the workflow document types view to use + the new AddRemove view. +* Add the workflow created and edited events. 3.1.11 (2019-04-XX) =================== diff --git a/docs/releases/3.2.rst b/docs/releases/3.2.rst index 15a94dbb50..74ec304465 100644 --- a/docs/releases/3.2.rst +++ b/docs/releases/3.2.rst @@ -256,6 +256,9 @@ Other changes * Remove mayan_permission_attribute_check from API permission. * Update Bootstrap and Bootswatch to version 3.4.1. +* Convert the workflow document types view to use + the new AddRemove view. +* Add the workflow created and edited events. Removals -------- diff --git a/mayan/apps/document_states/apps.py b/mayan/apps/document_states/apps.py index e843c04b09..1cae86216d 100644 --- a/mayan/apps/document_states/apps.py +++ b/mayan/apps/document_states/apps.py @@ -17,10 +17,15 @@ from mayan.apps.common.menus import ( menu_setup, menu_tools ) from mayan.apps.common.permissions_runtime import permission_error_log_view +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 mayan.celery import app from .classes import DocumentStateHelper, WorkflowAction +from .events import event_workflow_created, event_workflow_edited from .dependencies import * # NOQA from .handlers import ( handler_index_document, handler_launch_workflow, handler_trigger_transition @@ -61,6 +66,7 @@ class DocumentStatesApp(MayanAppConfig): def ready(self): super(DocumentStatesApp, self).ready() + from actstream import registry Action = apps.get_model( app_label='actstream', model_name='Action' @@ -107,6 +113,13 @@ class DocumentStatesApp(MayanAppConfig): 'selected workflow' ) ) + + ModelEventType.register( + event_types=( + event_workflow_created, event_workflow_edited + ), model=Workflow + ) + ModelPermission.register( model=Document, permissions=(permission_workflow_view,) ) @@ -260,9 +273,11 @@ class DocumentStatesApp(MayanAppConfig): ) menu_list_facet.bind_links( links=( + link_acl_list, link_events_for_object, + link_object_event_types_user_subcriptions_list, link_setup_workflow_document_types, link_setup_workflow_states, link_setup_workflow_transitions, - link_workflow_preview, link_acl_list + link_workflow_preview ), sources=(Workflow,) ) menu_main.bind_links(links=(link_workflow_runtime_proxy_list,), position=10) @@ -369,3 +384,5 @@ class DocumentStatesApp(MayanAppConfig): receiver=handler_trigger_transition, sender=Action ) + + registry.register(Workflow) diff --git a/mayan/apps/document_states/events.py b/mayan/apps/document_states/events.py new file mode 100644 index 0000000000..d6e26a72c6 --- /dev/null +++ b/mayan/apps/document_states/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=_('Workflows'), name='document_states' +) + +event_workflow_created = namespace.add_event_type( + label=_('Workflow created'), name='workflow_created' +) +event_workflow_edited = namespace.add_event_type( + label=_('Workflow edited'), name='workflow_edited' +) diff --git a/mayan/apps/document_states/models.py b/mayan/apps/document_states/models.py index 622922b64f..6d7f7c1050 100644 --- a/mayan/apps/document_states/models.py +++ b/mayan/apps/document_states/models.py @@ -7,7 +7,7 @@ from graphviz import Digraph from django.conf import settings from django.core.exceptions import PermissionDenied, ValidationError -from django.db import IntegrityError, models +from django.db import IntegrityError, models, transaction from django.db.models import F, Max, Q from django.urls import reverse from django.utils.encoding import force_text, python_2_unicode_compatible @@ -20,6 +20,7 @@ from mayan.apps.documents.models import Document, DocumentType from mayan.apps.events.models import StoredEventType from .error_logs import error_log_state_actions +from .events import event_workflow_created, event_workflow_edited from .literals import ( WORKFLOW_ACTION_WHEN_CHOICES, WORKFLOW_ACTION_ON_ENTRY, WORKFLOW_ACTION_ON_EXIT @@ -133,6 +134,21 @@ class Workflow(models.Model): return diagram.pipe() + def save(self, *args, **kwargs): + _user = kwargs.pop('_user', None) + + with transaction.atomic(): + is_new = not self.pk + super(Workflow, self).save(*args, **kwargs) + if is_new: + event_workflow_created.commit( + actor=_user, target=self + ) + else: + event_workflow_edited.commit( + actor=_user, target=self + ) + @python_2_unicode_compatible class WorkflowState(models.Model): diff --git a/mayan/apps/document_states/views/workflow_views.py b/mayan/apps/document_states/views/workflow_views.py index d160b7386e..62c80dfc1f 100644 --- a/mayan/apps/document_states/views/workflow_views.py +++ b/mayan/apps/document_states/views/workflow_views.py @@ -12,15 +12,19 @@ from django.utils.translation import ugettext_lazy as _ from mayan.apps.acls.models import AccessControlList from mayan.apps.common.generics import ( - AssignRemoveView, ConfirmView, FormView, SingleObjectCreateView, + AddRemoveView, ConfirmView, FormView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectDetailView, SingleObjectDynamicFormCreateView, SingleObjectDynamicFormEditView, SingleObjectDownloadView, SingleObjectEditView, SingleObjectListView ) +from mayan.apps.documents.events import event_document_type_edited +from mayan.apps.documents.models import DocumentType +from mayan.apps.documents.permissions import permission_document_type_edit from mayan.apps.events.classes import EventType from mayan.apps.events.models import StoredEventType from ..classes import WorkflowAction +from ..events import event_workflow_edited from ..forms import ( WorkflowActionSelectionForm, WorkflowForm, WorkflowPreviewForm, WorkflowStateActionDynamicForm, WorkflowStateForm, WorkflowTransitionForm, @@ -86,37 +90,51 @@ class SetupWorkflowListView(SingleObjectListView): class SetupWorkflowCreateView(SingleObjectCreateView): form_class = WorkflowForm model = Workflow - post_action_redirect = reverse_lazy('document_states:setup_workflow_list') + post_action_redirect = reverse_lazy( + viewname='document_states:setup_workflow_list' + ) view_permission = permission_workflow_create + def get_actions_extra_kwargs(self): + return {'_user': self.request.user} + class SetupWorkflowEditView(SingleObjectEditView): form_class = WorkflowForm model = Workflow object_permission = permission_workflow_edit - post_action_redirect = reverse_lazy('document_states:setup_workflow_list') + post_action_redirect = reverse_lazy( + viewname='document_states:setup_workflow_list' + ) + + def get_actions_extra_kwargs(self): + return {'_user': self.request.user} class SetupWorkflowDeleteView(SingleObjectDeleteView): model = Workflow object_permission = permission_workflow_delete - post_action_redirect = reverse_lazy('document_states:setup_workflow_list') + post_action_redirect = reverse_lazy( + viewname='document_states:setup_workflow_list' + ) -class SetupWorkflowDocumentTypesView(AssignRemoveView): - decode_content_type = True - left_list_title = _('Available document types') - object_permission = permission_workflow_edit - right_list_title = _('Document types assigned this workflow') +class SetupWorkflowDocumentTypesView(AddRemoveView): + main_object_permission = permission_workflow_edit + main_object_model = Workflow + 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 assigned this workflow') + related_field = 'document_types' - def add(self, item): - self.get_object().document_types.add(item) - # TODO: add task launching this workflow for all the document types - # of 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, 'subtitle': _( 'Removing a document type from a workflow will also ' 'remove all running instances of that workflow for ' @@ -124,28 +142,36 @@ class SetupWorkflowDocumentTypesView(AssignRemoveView): ), 'title': _( 'Document types assigned the workflow: %s' - ) % self.get_object(), + ) % self.main_object, } - def get_object(self): - return get_object_or_404(klass=Workflow, pk=self.kwargs['pk']) - - def left_list(self): - return AssignRemoveView.generate_choices( - self.get_object().get_document_types_not_in_workflow() - ) - - def right_list(self): - return AssignRemoveView.generate_choices( - self.get_object().document_types.all() - ) - - def remove(self, item): - # When removing a document type to workflow association - # also remove all running workflows in documents of that type. + def action_add(self, queryset, _user): with transaction.atomic(): - self.get_object().document_types.remove(item) - self.get_object().instances.filter(document__document_type=item).delete() + event_workflow_edited.commit( + actor=_user, target=self.main_object + ) + + for obj in queryset: + self.main_object.document_types.add(obj) + event_document_type_edited.commit( + action_object=self.main_object, actor=_user, target=obj + ) + + def action_remove(self, queryset, _user): + with transaction.atomic(): + event_workflow_edited.commit( + actor=_user, target=self.main_object + ) + + for obj in queryset: + self.main_object.document_types.remove(obj) + event_document_type_edited.commit( + action_object=self.main_object, actor=_user, + target=obj + ) + self.main_object.instances.filter( + document__document_type=obj + ).delete() # Workflow state actions @@ -193,8 +219,8 @@ class SetupWorkflowStateActionCreateView(SingleObjectDynamicFormCreateView): def get_post_action_redirect(self): return reverse( - 'document_states:setup_workflow_state_action_list', - args=(self.get_object().pk,) + viewname='document_states:setup_workflow_state_action_list', + kwargs={'pk': self.get_object().pk} ) @@ -215,8 +241,8 @@ class SetupWorkflowStateActionDeleteView(SingleObjectDeleteView): def get_post_action_redirect(self): return reverse( - 'document_states:setup_workflow_state_action_list', - args=(self.get_object().state.pk,) + viewname='document_states:setup_workflow_state_action_list', + kwargs={'pk': self.get_object().state.pk} ) @@ -249,8 +275,8 @@ class SetupWorkflowStateActionEditView(SingleObjectDynamicFormEditView): def get_post_action_redirect(self): return reverse( - 'document_states:setup_workflow_state_action_list', - args=(self.get_object().state.pk,) + viewname='document_states:setup_workflow_state_action_list', + kwargs={'pk': self.get_object().state.pk} ) @@ -300,9 +326,9 @@ class SetupWorkflowStateActionSelectionView(FormView): def form_valid(self, form): klass = form.cleaned_data['klass'] return HttpResponseRedirect( - reverse( - 'document_states:setup_workflow_state_action_create', - args=(self.get_object().pk, klass,), + redirect_to=reverse( + viewname='document_states:setup_workflow_state_action_create', + kwargs={'pk': self.get_object().pk, 'class_path': klass} ) ) @@ -339,7 +365,9 @@ class SetupWorkflowStateCreateView(SingleObjectCreateView): def get_success_url(self): return reverse( - 'document_states:setup_workflow_state_list', args=(self.kwargs['pk'],) + viewname='document_states:setup_workflow_state_list', kwargs={ + 'pk': self.kwargs['pk'] + } ) def get_workflow(self): @@ -373,8 +401,8 @@ class SetupWorkflowStateDeleteView(SingleObjectDeleteView): def get_success_url(self): return reverse( - 'document_states:setup_workflow_state_list', - args=(self.get_object().workflow.pk,) + viewname='document_states:setup_workflow_state_list', + kwargs={'pk': self.get_object().workflow.pk} ) def get_workflow(self): @@ -400,8 +428,8 @@ class SetupWorkflowStateEditView(SingleObjectEditView): def get_success_url(self): return reverse( - 'document_states:setup_workflow_state_list', - args=(self.get_object().workflow.pk,) + viewname='document_states:setup_workflow_state_list', + kwargs={'pk': self.get_object().workflow.pk} ) @@ -457,7 +485,8 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView): self.object.save() except IntegrityError: messages.error( - self.request, _('Unable to save transition; integrity error.') + message=_('Unable to save transition; integrity error.'), + request=self.request ) return super( SetupWorkflowTransitionCreateView, self @@ -485,8 +514,8 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView): def get_success_url(self): return reverse( - 'document_states:setup_workflow_transition_list', - args=(self.kwargs['pk'],) + viewname='document_states:setup_workflow_transition_list', + kwargs={'pk': self.kwargs['pk']} ) def get_workflow(self): @@ -511,8 +540,8 @@ class SetupWorkflowTransitionDeleteView(SingleObjectDeleteView): def get_success_url(self): return reverse( - 'document_states:setup_workflow_transition_list', - args=(self.get_object().workflow.pk,) + viewname='document_states:setup_workflow_transition_list', + kwargs={'pk': self.get_object().workflow.pk} ) @@ -537,8 +566,8 @@ class SetupWorkflowTransitionEditView(SingleObjectEditView): def get_success_url(self): return reverse( - 'document_states:setup_workflow_transition_list', - args=(self.get_object().workflow.pk,) + viewname='document_states:setup_workflow_transition_list', + kwargs={'pk': self.get_object().workflow.pk} ) @@ -596,16 +625,15 @@ class SetupWorkflowTransitionTriggerEventListView(FormView): instance.save() except Exception as exception: messages.error( - self.request, - _( + message=_( 'Error updating workflow transition trigger events; %s' - ) % exception + ) % exception, request=self.request ) else: messages.success( - self.request, _( + message=_( 'Workflow transition trigger events updated successfully' - ) + ), request=self.request ) return super( @@ -649,8 +677,8 @@ class SetupWorkflowTransitionTriggerEventListView(FormView): def get_post_action_redirect(self): return reverse( - 'document_states:setup_workflow_transition_list', - args=(self.get_object().workflow.pk,) + viewname='document_states:setup_workflow_transition_list', + kwargs={'pk': self.get_object().workflow.pk} ) @@ -667,7 +695,8 @@ class ToolLaunchAllWorkflows(ConfirmView): def view_action(self): task_launch_all_workflows.apply_async() messages.success( - self.request, _('Workflow launch queued successfully.') + message=_('Workflow launch queued successfully.'), + request=self.request )