Convert workflow document types to AddRemove view

Add worflow created and edited events.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2019-05-04 20:47:21 -04:00
parent 9c92b9a59e
commit 3d240a7f42
6 changed files with 148 additions and 64 deletions

View File

@@ -223,6 +223,9 @@
* Remove mayan_permission_attribute_check from * Remove mayan_permission_attribute_check from
API permission. API permission.
* Update Bootstrap and Bootswatch to version 3.4.1. * 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) 3.1.11 (2019-04-XX)
=================== ===================

View File

@@ -256,6 +256,9 @@ Other changes
* Remove mayan_permission_attribute_check from * Remove mayan_permission_attribute_check from
API permission. API permission.
* Update Bootstrap and Bootswatch to version 3.4.1. * 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 Removals
-------- --------

View File

@@ -17,10 +17,15 @@ from mayan.apps.common.menus import (
menu_setup, menu_tools menu_setup, menu_tools
) )
from mayan.apps.common.permissions_runtime import permission_error_log_view 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.apps.navigation.classes import SourceColumn
from mayan.celery import app from mayan.celery import app
from .classes import DocumentStateHelper, WorkflowAction from .classes import DocumentStateHelper, WorkflowAction
from .events import event_workflow_created, event_workflow_edited
from .dependencies import * # NOQA from .dependencies import * # NOQA
from .handlers import ( from .handlers import (
handler_index_document, handler_launch_workflow, handler_trigger_transition handler_index_document, handler_launch_workflow, handler_trigger_transition
@@ -61,6 +66,7 @@ class DocumentStatesApp(MayanAppConfig):
def ready(self): def ready(self):
super(DocumentStatesApp, self).ready() super(DocumentStatesApp, self).ready()
from actstream import registry
Action = apps.get_model( Action = apps.get_model(
app_label='actstream', model_name='Action' app_label='actstream', model_name='Action'
@@ -107,6 +113,13 @@ class DocumentStatesApp(MayanAppConfig):
'selected workflow' 'selected workflow'
) )
) )
ModelEventType.register(
event_types=(
event_workflow_created, event_workflow_edited
), model=Workflow
)
ModelPermission.register( ModelPermission.register(
model=Document, permissions=(permission_workflow_view,) model=Document, permissions=(permission_workflow_view,)
) )
@@ -260,9 +273,11 @@ class DocumentStatesApp(MayanAppConfig):
) )
menu_list_facet.bind_links( menu_list_facet.bind_links(
links=( links=(
link_acl_list, link_events_for_object,
link_object_event_types_user_subcriptions_list,
link_setup_workflow_document_types, link_setup_workflow_document_types,
link_setup_workflow_states, link_setup_workflow_transitions, link_setup_workflow_states, link_setup_workflow_transitions,
link_workflow_preview, link_acl_list link_workflow_preview
), sources=(Workflow,) ), sources=(Workflow,)
) )
menu_main.bind_links(links=(link_workflow_runtime_proxy_list,), position=10) menu_main.bind_links(links=(link_workflow_runtime_proxy_list,), position=10)
@@ -369,3 +384,5 @@ class DocumentStatesApp(MayanAppConfig):
receiver=handler_trigger_transition, receiver=handler_trigger_transition,
sender=Action sender=Action
) )
registry.register(Workflow)

View File

@@ -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'
)

View File

@@ -7,7 +7,7 @@ from graphviz import Digraph
from django.conf import settings from django.conf import settings
from django.core.exceptions import PermissionDenied, ValidationError 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.db.models import F, Max, Q
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_text, python_2_unicode_compatible 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 mayan.apps.events.models import StoredEventType
from .error_logs import error_log_state_actions from .error_logs import error_log_state_actions
from .events import event_workflow_created, event_workflow_edited
from .literals import ( from .literals import (
WORKFLOW_ACTION_WHEN_CHOICES, WORKFLOW_ACTION_ON_ENTRY, WORKFLOW_ACTION_WHEN_CHOICES, WORKFLOW_ACTION_ON_ENTRY,
WORKFLOW_ACTION_ON_EXIT WORKFLOW_ACTION_ON_EXIT
@@ -133,6 +134,21 @@ class Workflow(models.Model):
return diagram.pipe() 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 @python_2_unicode_compatible
class WorkflowState(models.Model): class WorkflowState(models.Model):

View File

@@ -12,15 +12,19 @@ from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.models import AccessControlList from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.generics import ( from mayan.apps.common.generics import (
AssignRemoveView, ConfirmView, FormView, SingleObjectCreateView, AddRemoveView, ConfirmView, FormView, SingleObjectCreateView,
SingleObjectDeleteView, SingleObjectDetailView, SingleObjectDeleteView, SingleObjectDetailView,
SingleObjectDynamicFormCreateView, SingleObjectDynamicFormEditView, SingleObjectDynamicFormCreateView, SingleObjectDynamicFormEditView,
SingleObjectDownloadView, SingleObjectEditView, SingleObjectListView 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.classes import EventType
from mayan.apps.events.models import StoredEventType from mayan.apps.events.models import StoredEventType
from ..classes import WorkflowAction from ..classes import WorkflowAction
from ..events import event_workflow_edited
from ..forms import ( from ..forms import (
WorkflowActionSelectionForm, WorkflowForm, WorkflowPreviewForm, WorkflowActionSelectionForm, WorkflowForm, WorkflowPreviewForm,
WorkflowStateActionDynamicForm, WorkflowStateForm, WorkflowTransitionForm, WorkflowStateActionDynamicForm, WorkflowStateForm, WorkflowTransitionForm,
@@ -86,37 +90,51 @@ class SetupWorkflowListView(SingleObjectListView):
class SetupWorkflowCreateView(SingleObjectCreateView): class SetupWorkflowCreateView(SingleObjectCreateView):
form_class = WorkflowForm form_class = WorkflowForm
model = Workflow 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 view_permission = permission_workflow_create
def get_actions_extra_kwargs(self):
return {'_user': self.request.user}
class SetupWorkflowEditView(SingleObjectEditView): class SetupWorkflowEditView(SingleObjectEditView):
form_class = WorkflowForm form_class = WorkflowForm
model = Workflow model = Workflow
object_permission = permission_workflow_edit 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): class SetupWorkflowDeleteView(SingleObjectDeleteView):
model = Workflow model = Workflow
object_permission = permission_workflow_delete 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): class SetupWorkflowDocumentTypesView(AddRemoveView):
decode_content_type = True main_object_permission = permission_workflow_edit
left_list_title = _('Available document types') main_object_model = Workflow
object_permission = permission_workflow_edit main_object_pk_url_kwarg = 'pk'
right_list_title = _('Document types assigned this workflow') 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): def get_actions_extra_kwargs(self):
self.get_object().document_types.add(item) return {'_user': self.request.user}
# TODO: add task launching this workflow for all the document types
# of item
def get_extra_context(self): def get_extra_context(self):
return { return {
'object': self.get_object(), 'object': self.main_object,
'subtitle': _( 'subtitle': _(
'Removing a document type from a workflow will also ' 'Removing a document type from a workflow will also '
'remove all running instances of that workflow for ' 'remove all running instances of that workflow for '
@@ -124,28 +142,36 @@ class SetupWorkflowDocumentTypesView(AssignRemoveView):
), ),
'title': _( 'title': _(
'Document types assigned the workflow: %s' 'Document types assigned the workflow: %s'
) % self.get_object(), ) % self.main_object,
} }
def get_object(self): def action_add(self, queryset, _user):
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.
with transaction.atomic(): with transaction.atomic():
self.get_object().document_types.remove(item) event_workflow_edited.commit(
self.get_object().instances.filter(document__document_type=item).delete() 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 # Workflow state actions
@@ -193,8 +219,8 @@ class SetupWorkflowStateActionCreateView(SingleObjectDynamicFormCreateView):
def get_post_action_redirect(self): def get_post_action_redirect(self):
return reverse( return reverse(
'document_states:setup_workflow_state_action_list', viewname='document_states:setup_workflow_state_action_list',
args=(self.get_object().pk,) kwargs={'pk': self.get_object().pk}
) )
@@ -215,8 +241,8 @@ class SetupWorkflowStateActionDeleteView(SingleObjectDeleteView):
def get_post_action_redirect(self): def get_post_action_redirect(self):
return reverse( return reverse(
'document_states:setup_workflow_state_action_list', viewname='document_states:setup_workflow_state_action_list',
args=(self.get_object().state.pk,) kwargs={'pk': self.get_object().state.pk}
) )
@@ -249,8 +275,8 @@ class SetupWorkflowStateActionEditView(SingleObjectDynamicFormEditView):
def get_post_action_redirect(self): def get_post_action_redirect(self):
return reverse( return reverse(
'document_states:setup_workflow_state_action_list', viewname='document_states:setup_workflow_state_action_list',
args=(self.get_object().state.pk,) kwargs={'pk': self.get_object().state.pk}
) )
@@ -300,9 +326,9 @@ class SetupWorkflowStateActionSelectionView(FormView):
def form_valid(self, form): def form_valid(self, form):
klass = form.cleaned_data['klass'] klass = form.cleaned_data['klass']
return HttpResponseRedirect( return HttpResponseRedirect(
reverse( redirect_to=reverse(
'document_states:setup_workflow_state_action_create', viewname='document_states:setup_workflow_state_action_create',
args=(self.get_object().pk, klass,), kwargs={'pk': self.get_object().pk, 'class_path': klass}
) )
) )
@@ -339,7 +365,9 @@ class SetupWorkflowStateCreateView(SingleObjectCreateView):
def get_success_url(self): def get_success_url(self):
return reverse( 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): def get_workflow(self):
@@ -373,8 +401,8 @@ class SetupWorkflowStateDeleteView(SingleObjectDeleteView):
def get_success_url(self): def get_success_url(self):
return reverse( return reverse(
'document_states:setup_workflow_state_list', viewname='document_states:setup_workflow_state_list',
args=(self.get_object().workflow.pk,) kwargs={'pk': self.get_object().workflow.pk}
) )
def get_workflow(self): def get_workflow(self):
@@ -400,8 +428,8 @@ class SetupWorkflowStateEditView(SingleObjectEditView):
def get_success_url(self): def get_success_url(self):
return reverse( return reverse(
'document_states:setup_workflow_state_list', viewname='document_states:setup_workflow_state_list',
args=(self.get_object().workflow.pk,) kwargs={'pk': self.get_object().workflow.pk}
) )
@@ -457,7 +485,8 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView):
self.object.save() self.object.save()
except IntegrityError: except IntegrityError:
messages.error( messages.error(
self.request, _('Unable to save transition; integrity error.') message=_('Unable to save transition; integrity error.'),
request=self.request
) )
return super( return super(
SetupWorkflowTransitionCreateView, self SetupWorkflowTransitionCreateView, self
@@ -485,8 +514,8 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView):
def get_success_url(self): def get_success_url(self):
return reverse( return reverse(
'document_states:setup_workflow_transition_list', viewname='document_states:setup_workflow_transition_list',
args=(self.kwargs['pk'],) kwargs={'pk': self.kwargs['pk']}
) )
def get_workflow(self): def get_workflow(self):
@@ -511,8 +540,8 @@ class SetupWorkflowTransitionDeleteView(SingleObjectDeleteView):
def get_success_url(self): def get_success_url(self):
return reverse( return reverse(
'document_states:setup_workflow_transition_list', viewname='document_states:setup_workflow_transition_list',
args=(self.get_object().workflow.pk,) kwargs={'pk': self.get_object().workflow.pk}
) )
@@ -537,8 +566,8 @@ class SetupWorkflowTransitionEditView(SingleObjectEditView):
def get_success_url(self): def get_success_url(self):
return reverse( return reverse(
'document_states:setup_workflow_transition_list', viewname='document_states:setup_workflow_transition_list',
args=(self.get_object().workflow.pk,) kwargs={'pk': self.get_object().workflow.pk}
) )
@@ -596,16 +625,15 @@ class SetupWorkflowTransitionTriggerEventListView(FormView):
instance.save() instance.save()
except Exception as exception: except Exception as exception:
messages.error( messages.error(
self.request, message=_(
_(
'Error updating workflow transition trigger events; %s' 'Error updating workflow transition trigger events; %s'
) % exception ) % exception, request=self.request
) )
else: else:
messages.success( messages.success(
self.request, _( message=_(
'Workflow transition trigger events updated successfully' 'Workflow transition trigger events updated successfully'
) ), request=self.request
) )
return super( return super(
@@ -649,8 +677,8 @@ class SetupWorkflowTransitionTriggerEventListView(FormView):
def get_post_action_redirect(self): def get_post_action_redirect(self):
return reverse( return reverse(
'document_states:setup_workflow_transition_list', viewname='document_states:setup_workflow_transition_list',
args=(self.get_object().workflow.pk,) kwargs={'pk': self.get_object().workflow.pk}
) )
@@ -667,7 +695,8 @@ class ToolLaunchAllWorkflows(ConfirmView):
def view_action(self): def view_action(self):
task_launch_all_workflows.apply_async() task_launch_all_workflows.apply_async()
messages.success( messages.success(
self.request, _('Workflow launch queued successfully.') message=_('Workflow launch queued successfully.'),
request=self.request
) )