From e1a63064dcb9491f20fcc5e98e8c90d4e4e59280 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Sun, 30 Jun 2019 09:51:22 -0400 Subject: [PATCH] Proof of concept of the workflow instance context Add support for workflow instance JSON context. Add support for two step workflow transition. Add support for dynamic form creation for transition execution. Signed-off-by: Roberto Rosario --- mayan/apps/document_states/apps.py | 33 ++++- mayan/apps/document_states/forms.py | 12 +- mayan/apps/document_states/icons.py | 10 ++ mayan/apps/document_states/links.py | 31 ++++- .../migrations/0014_auto_20190630_1331.py | 40 ++++++ mayan/apps/document_states/models.py | 127 +++++++++++++----- mayan/apps/document_states/urls.py | 120 +++++++++++------ .../views/workflow_instance_views.py | 108 +++++++++++++-- .../document_states/views/workflow_views.py | 115 +++++++++++++++- 9 files changed, 499 insertions(+), 97 deletions(-) create mode 100644 mayan/apps/document_states/migrations/0014_auto_20190630_1331.py diff --git a/mayan/apps/document_states/apps.py b/mayan/apps/document_states/apps.py index f7e02372d9..65186bb408 100644 --- a/mayan/apps/document_states/apps.py +++ b/mayan/apps/document_states/apps.py @@ -40,6 +40,10 @@ from .links import ( link_setup_workflow_state_edit, link_setup_workflow_transitions, link_setup_workflow_transition_create, link_setup_workflow_transition_delete, link_setup_workflow_transition_edit, + link_setup_workflow_transition_field_create, + link_setup_workflow_transition_field_delete, + link_setup_workflow_transition_field_edit, + link_setup_workflow_transition_field_list, link_tool_launch_all_workflows, link_workflow_instance_detail, link_workflow_instance_transition, link_workflow_runtime_proxy_document_list, link_workflow_runtime_proxy_list, link_workflow_preview, @@ -86,6 +90,7 @@ class DocumentStatesApp(MayanAppConfig): WorkflowStateAction = self.get_model('WorkflowStateAction') WorkflowStateRuntimeProxy = self.get_model('WorkflowStateRuntimeProxy') WorkflowTransition = self.get_model('WorkflowTransition') + WorkflowTransitionField = self.get_model('WorkflowTransitionField') WorkflowTransitionTriggerEvent = self.get_model( 'WorkflowTransitionTriggerEvent' ) @@ -257,6 +262,18 @@ class DocumentStatesApp(MayanAppConfig): ) ) + SourceColumn( + attribute='name', is_identifier=True, is_sortable=True, + source=WorkflowTransitionField + ) + SourceColumn( + attribute='label', is_sortable=True, source=WorkflowTransitionField + ) + SourceColumn( + attribute='required', is_sortable=True, source=WorkflowTransitionField, + widget=TwoStateWidget + ) + SourceColumn( source=WorkflowRuntimeProxy, label=_('Documents'), func=lambda context: context['object'].get_document_count( @@ -305,10 +322,18 @@ class DocumentStatesApp(MayanAppConfig): menu_object.bind_links( links=( link_setup_workflow_transition_edit, - link_workflow_transition_events, link_acl_list, + link_workflow_transition_events, + link_setup_workflow_transition_field_list, + link_acl_list, link_setup_workflow_transition_delete ), sources=(WorkflowTransition,) ) + menu_object.bind_links( + links=( + link_setup_workflow_transition_field_delete, + link_setup_workflow_transition_field_edit + ), sources=(WorkflowTransitionField,) + ) menu_object.bind_links( links=( link_workflow_instance_detail, @@ -342,6 +367,12 @@ class DocumentStatesApp(MayanAppConfig): 'document_states:setup_workflow_list' ) ) + menu_secondary.bind_links( + links=(link_setup_workflow_transition_field_create,), + sources=( + WorkflowTransition, + ) + ) menu_secondary.bind_links( links=(link_workflow_runtime_proxy_list,), sources=( diff --git a/mayan/apps/document_states/forms.py b/mayan/apps/document_states/forms.py index 971b17bd2d..dfb2e6fd65 100644 --- a/mayan/apps/document_states/forms.py +++ b/mayan/apps/document_states/forms.py @@ -165,11 +165,11 @@ WorkflowTransitionTriggerEventRelationshipFormSet = formset_factory( ) -class WorkflowInstanceTransitionForm(forms.Form): +class WorkflowInstanceTransitionSelectForm(forms.Form): def __init__(self, *args, **kwargs): user = kwargs.pop('user') workflow_instance = kwargs.pop('workflow_instance') - super(WorkflowInstanceTransitionForm, self).__init__(*args, **kwargs) + super(WorkflowInstanceTransitionSelectForm, self).__init__(*args, **kwargs) self.fields[ 'transition' ].queryset = workflow_instance.get_transition_choices(_user=user) @@ -177,14 +177,6 @@ class WorkflowInstanceTransitionForm(forms.Form): transition = forms.ModelChoiceField( label=_('Transition'), queryset=WorkflowTransition.objects.none() ) - comment = forms.CharField( - help_text=_('Optional comment to attach to the transition.'), - label=_('Comment'), required=False, widget=forms.widgets.Textarea( - attrs={ - 'rows': 3 - } - ) - ) class WorkflowPreviewForm(forms.Form): diff --git a/mayan/apps/document_states/icons.py b/mayan/apps/document_states/icons.py index 8ae3b8e990..db6c5b5925 100644 --- a/mayan/apps/document_states/icons.py +++ b/mayan/apps/document_states/icons.py @@ -76,6 +76,16 @@ icon_workflow_transition_delete = Icon(driver_name='fontawesome', symbol='times' icon_workflow_transition_edit = Icon( driver_name='fontawesome', symbol='pencil-alt' ) + +icon_workflow_transition_field = Icon(driver_name='fontawesome', symbol='code') +icon_workflow_transition_field_delete = Icon(driver_name='fontawesome', symbol='times') +icon_workflow_transition_field_edit = Icon(driver_name='fontawesome', symbol='pencil-alt') +icon_workflow_transition_field_create = Icon( + driver_name='fontawesome-dual', primary_symbol='code', + secondary_symbol='plus' +) +icon_workflow_transition_field_list = Icon(driver_name='fontawesome', symbol='code') + icon_workflow_transition_triggers = Icon( driver_name='fontawesome', symbol='bolt' ) diff --git a/mayan/apps/document_states/links.py b/mayan/apps/document_states/links.py index 3d23efec95..1404ff2644 100644 --- a/mayan/apps/document_states/links.py +++ b/mayan/apps/document_states/links.py @@ -129,6 +129,35 @@ link_workflow_transition_events = Link( text=_('Transition triggers'), view='document_states:setup_workflow_transition_events' ) + +# Workflow transition fields +link_setup_workflow_transition_field_create = Link( + args='resolved_object.pk', + icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field', + permissions=(permission_workflow_edit,), text=_('Create field'), + view='document_states:setup_workflow_transition_field_create', +) +link_setup_workflow_transition_field_delete = Link( + args='resolved_object.pk', + icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field_delete', + permissions=(permission_workflow_edit,), + tags='dangerous', text=_('Delete'), + view='document_states:setup_workflow_transition_field_delete', +) +link_setup_workflow_transition_field_edit = Link( + args='resolved_object.pk', + icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field_edit', + permissions=(permission_workflow_edit,), + text=_('Edit'), view='document_states:setup_workflow_transition_field_edit', +) +link_setup_workflow_transition_field_list = Link( + args='resolved_object.pk', + icon_class_path='mayan.apps.document_states.icons.icon_workflow_transition_field_list', + permissions=(permission_workflow_edit,), + text=_('Fields'), + view='document_states:setup_workflow_transition_field_list', +) + link_workflow_preview = Link( args='resolved_object.pk', icon_class_path='mayan.apps.document_states.icons.icon_workflow_preview', @@ -159,7 +188,7 @@ link_workflow_instance_transition = Link( args='resolved_object.pk', icon_class_path='mayan.apps.document_states.icons.icon_workflow_instance_transition', text=_('Transition'), - view='document_states:workflow_instance_transition', + view='document_states:workflow_instance_transition_selection', ) # Runtime proxies diff --git a/mayan/apps/document_states/migrations/0014_auto_20190630_1331.py b/mayan/apps/document_states/migrations/0014_auto_20190630_1331.py new file mode 100644 index 0000000000..8f4e567f9a --- /dev/null +++ b/mayan/apps/document_states/migrations/0014_auto_20190630_1331.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-06-30 13:31 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('document_states', '0013_auto_20190423_0810'), + ] + + operations = [ + migrations.CreateModel( + name='WorkflowTransitionField', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=128, verbose_name='Internal name')), + ('label', models.CharField(max_length=128, verbose_name='Label')), + ('help_text', models.TextField(blank=True, verbose_name='Help text')), + ('required', models.BooleanField(default=False, verbose_name='Required')), + ('transition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='document_states.WorkflowTransition', verbose_name='Transition')), + ], + options={ + 'verbose_name': 'Workflow transition trigger event', + 'verbose_name_plural': 'Workflow transitions trigger events', + }, + ), + migrations.AddField( + model_name='workflowinstance', + name='context', + field=models.TextField(blank=True, verbose_name='Backend data'), + ), + migrations.AlterUniqueTogether( + name='workflowtransitionfield', + unique_together=set([('transition', 'name')]), + ), + ] diff --git a/mayan/apps/document_states/models.py b/mayan/apps/document_states/models.py index d006dcf6c3..7a4c6647bd 100644 --- a/mayan/apps/document_states/models.py +++ b/mayan/apps/document_states/models.py @@ -257,8 +257,8 @@ class WorkflowState(models.Model): def save(self, *args, **kwargs): # Solve issue #557 "Break workflows with invalid input" # without using a migration. - # Remove blank=True, remove this, and create a migration in the next - # minor version. + # TODO: Remove blank=True, remove this, and create a migration in the + # next minor version. try: self.completion = int(self.completion) @@ -363,6 +363,44 @@ class WorkflowTransition(models.Model): return self.label +@python_2_unicode_compatible +class WorkflowTransitionField(models.Model): + transition = models.ForeignKey( + on_delete=models.CASCADE, related_name='fields', + to=WorkflowTransition, verbose_name=_('Transition') + ) + name = models.CharField( + help_text=_( + 'The name that will be used to identify this field in other parts ' + 'of the workflow system.' + ), max_length=128, verbose_name=_('Internal name') + ) + label = models.CharField( + help_text=_( + 'The field name that will be shown on the user interface.' + ), max_length=128, verbose_name=_('Label')) + help_text = models.TextField( + blank=True, help_text=_( + 'An optional message that will help users better understand the ' + 'purpose of the field and data to provide.' + ), verbose_name=_('Help text') + ) + required = models.BooleanField( + default=False, help_text=_( + 'Whether this fields needs to be filled out or not to proceed.' + ), verbose_name=_('Required') + ) + #TODO: widget, widget kwargs + + class Meta: + unique_together = ('transition', 'name') + verbose_name = _('Workflow transition trigger event') + verbose_name_plural = _('Workflow transitions trigger events') + + def __str__(self): + return self.label + + @python_2_unicode_compatible class WorkflowTransitionTriggerEvent(models.Model): transition = models.ForeignKey( @@ -392,6 +430,9 @@ class WorkflowInstance(models.Model): on_delete=models.CASCADE, related_name='workflows', to=Document, verbose_name=_('Document') ) + context = models.TextField( + blank=True, verbose_name=_('Backend data') + ) class Meta: ordering = ('workflow',) @@ -402,15 +443,30 @@ class WorkflowInstance(models.Model): def __str__(self): return force_text(self.workflow) - def do_transition(self, transition, user=None, comment=None): - try: - if transition in self.get_current_state().origin_transitions.all(): - self.log_entries.create( - comment=comment or '', transition=transition, user=user - ) - except AttributeError: - # No initial state has been set for this workflow - pass + def do_transition(self, transition, extra_data=None, user=None, comment=None): + with transaction.atomic(): + try: + if transition in self.get_current_state().origin_transitions.all(): + self.log_entries.create( + comment=comment or '', transition=transition, user=user + ) + if extra_data: + data = self.loads() + data.update(extra_data) + self.dumps(data=data) + except AttributeError: + # No initial state has been set for this workflow + pass + + # TODO: execute transition event target = document, + # action_object = self + + def dumps(self, data): + """ + Serialize the context data. + """ + self.context = json.dumps(data) + self.save() def get_absolute_url(self): return reverse( @@ -420,10 +476,12 @@ class WorkflowInstance(models.Model): ) def get_context(self): - return { + context = { 'document': self.document, 'workflow': self.workflow, 'workflow_instance': self, } + context['workflow_instance_context'] = self.loads() + return context def get_current_state(self): """ @@ -489,6 +547,12 @@ class WorkflowInstance(models.Model): """ return WorkflowTransition.objects.none() + def loads(self): + """ + Deserialize the context data. + """ + return json.loads(self.context or '{}') + @python_2_unicode_compatible class WorkflowInstanceLogEntry(models.Model): @@ -529,31 +593,32 @@ class WorkflowInstanceLogEntry(models.Model): raise ValidationError(_('Not a valid transition choice.')) def save(self, *args, **kwargs): - result = super(WorkflowInstanceLogEntry, self).save(*args, **kwargs) - context = self.workflow_instance.get_context() - context.update( - { - 'entry_log': self - } - ) - - for action in self.transition.origin_state.exit_actions.filter(enabled=True): + with transaction.atomic(): + result = super(WorkflowInstanceLogEntry, self).save(*args, **kwargs) + context = self.workflow_instance.get_context() context.update( { - 'action': action, + 'entry_log': self } ) - action.execute(context=context) - for action in self.transition.destination_state.entry_actions.filter(enabled=True): - context.update( - { - 'action': action, - } - ) - action.execute(context=context) + for action in self.transition.origin_state.exit_actions.filter(enabled=True): + context.update( + { + 'action': action, + } + ) + action.execute(context=context) - return result + for action in self.transition.destination_state.entry_actions.filter(enabled=True): + context.update( + { + 'action': action, + } + ) + action.execute(context=context) + + return result class WorkflowRuntimeProxy(Workflow): diff --git a/mayan/apps/document_states/urls.py b/mayan/apps/document_states/urls.py index e139e3ff8a..49ccd4f19c 100644 --- a/mayan/apps/document_states/urls.py +++ b/mayan/apps/document_states/urls.py @@ -22,19 +22,86 @@ from .views import ( SetupWorkflowTransitionEditView, SetupWorkflowTransitionTriggerEventListView, ToolLaunchAllWorkflows, WorkflowDocumentListView, WorkflowInstanceDetailView, - WorkflowImageView, WorkflowInstanceTransitionView, WorkflowListView, + WorkflowImageView, WorkflowInstanceTransitionExecuteView, + WorkflowInstanceTransitionSelectView, WorkflowListView, WorkflowPreviewView, WorkflowStateDocumentListView, WorkflowStateListView, ) -from .views.workflow_views import SetupDocumentTypeWorkflowsView +from .views.workflow_views import ( + SetupDocumentTypeWorkflowsView, SetupWorkflowTransitionFieldCreateView, + SetupWorkflowTransitionFieldDeleteView, + SetupWorkflowTransitionFieldEditView, SetupWorkflowTransitionFieldListView +) urlpatterns_workflows = [ url( - regex=r'^document_type/(?P\d+)/workflows/$', + regex=r'^setup/workflows/$', view=SetupWorkflowListView.as_view(), + name='setup_workflow_list' + ), + url( + regex=r'^setup/workflows/create/$', view=SetupWorkflowCreateView.as_view(), + name='setup_workflow_create' + ), + url( + regex=r'^setup/workflows/(?P\d+)/delete/$', + view=SetupWorkflowDeleteView.as_view(), name='setup_workflow_delete' + ), + url( + regex=r'^setup/workflows/(?P\d+)/edit/$', + view=SetupWorkflowEditView.as_view(), name='setup_workflow_edit' + ), + url( + regex=r'^setup/document_types/(?P\d+)/workflows/$', view=SetupDocumentTypeWorkflowsView.as_view(), name='document_type_workflows' ), ] +urlpatterns_workflow_states = [ + url( + regex=r'^setup/workflow/(?P\d+)/states/$', + view=SetupWorkflowStateListView.as_view(), + name='setup_workflow_state_list' + ), + url( + regex=r'^setup/workflow/(?P\d+)/states/create/$', + view=SetupWorkflowStateCreateView.as_view(), + name='setup_workflow_state_create' + ), + url( + regex=r'^setup/workflow/state/(?P\d+)/delete/$', + view=SetupWorkflowStateDeleteView.as_view(), + name='setup_workflow_state_delete' + ), + url( + regex=r'^setup/workflow/state/(?P\d+)/edit/$', + view=SetupWorkflowStateEditView.as_view(), + name='setup_workflow_state_edit' + ), +] + +urlpatterns_workflow_transition_fields = [ + url( + regex=r'^setup/workflows/transitions/(?P\d+)/fields/create/$', + view=SetupWorkflowTransitionFieldCreateView.as_view(), + name='setup_workflow_transition_field_create' + ), + url( + regex=r'^setup/workflows/transitions/(?P\d+)/fields/$', + view=SetupWorkflowTransitionFieldListView.as_view(), + name='setup_workflow_transition_field_list' + ), + url( + regex=r'^setup/workflows/transitions/fields/(?P\d+)/delete/$', + view=SetupWorkflowTransitionFieldDeleteView.as_view(), + name='setup_workflow_transition_field_delete' + ), + url( + regex=r'^setup/workflows/transitions/fields/(?P\d+)/edit/$', + view=SetupWorkflowTransitionFieldEditView.as_view(), + name='setup_workflow_transition_field_edit' + ), +] + urlpatterns = [ url( regex=r'^document/(?P\d+)/workflows/$', @@ -47,25 +114,14 @@ urlpatterns = [ name='workflow_instance_detail' ), url( - regex=r'^document/workflows/(?P\d+)/transition/$', - view=WorkflowInstanceTransitionView.as_view(), - name='workflow_instance_transition' + regex=r'^document/workflows/(?P\d+)/transitions/select/$', + view=WorkflowInstanceTransitionSelectView.as_view(), + name='workflow_instance_transition_selection' ), url( - regex=r'^setup/all/$', view=SetupWorkflowListView.as_view(), - name='setup_workflow_list' - ), - url( - regex=r'^setup/create/$', view=SetupWorkflowCreateView.as_view(), - name='setup_workflow_create' - ), - url( - regex=r'^setup/workflow/(?P\d+)/edit/$', - view=SetupWorkflowEditView.as_view(), name='setup_workflow_edit' - ), - url( - regex=r'^setup/workflow/(?P\d+)/delete/$', - view=SetupWorkflowDeleteView.as_view(), name='setup_workflow_delete' + regex=r'^document/workflows/(?P\d+)/transitions/(?P\d+)/execute/$', + view=WorkflowInstanceTransitionExecuteView.as_view(), + name='workflow_instance_transition_execute' ), url( regex=r'^setup/workflow/(?P\d+)/documents/$', @@ -77,16 +133,6 @@ urlpatterns = [ view=SetupWorkflowDocumentTypesView.as_view(), name='setup_workflow_document_types' ), - url( - regex=r'^setup/workflow/(?P\d+)/states/$', - view=SetupWorkflowStateListView.as_view(), - name='setup_workflow_state_list' - ), - url( - regex=r'^setup/workflow/(?P\d+)/states/create/$', - view=SetupWorkflowStateCreateView.as_view(), - name='setup_workflow_state_create' - ), url( regex=r'^setup/workflow/(?P\d+)/transitions/$', view=SetupWorkflowTransitionListView.as_view(), @@ -98,20 +144,10 @@ urlpatterns = [ name='setup_workflow_transition_create' ), url( - regex=r'^setup/workflow/(?P\d+)/transitions/events/$', + regex=r'^setup/workflow/transitions/(?P\d+)/events/$', view=SetupWorkflowTransitionTriggerEventListView.as_view(), name='setup_workflow_transition_events' ), - url( - regex=r'^setup/workflow/state/(?P\d+)/delete/$', - view=SetupWorkflowStateDeleteView.as_view(), - name='setup_workflow_state_delete' - ), - url( - regex=r'^setup/workflow/state/(?P\d+)/edit/$', - view=SetupWorkflowStateEditView.as_view(), - name='setup_workflow_state_edit' - ), url( regex=r'^setup/workflow/state/(?P\d+)/actions/$', view=SetupWorkflowStateActionListView.as_view(), @@ -184,6 +220,8 @@ urlpatterns = [ ), ] urlpatterns.extend(urlpatterns_workflows) +urlpatterns.extend(urlpatterns_workflow_states) +urlpatterns.extend(urlpatterns_workflow_transition_fields) api_urls = [ url( diff --git a/mayan/apps/document_states/views/workflow_instance_views.py b/mayan/apps/document_states/views/workflow_instance_views.py index 66750fc468..f857049829 100644 --- a/mayan/apps/document_states/views/workflow_instance_views.py +++ b/mayan/apps/document_states/views/workflow_instance_views.py @@ -4,13 +4,16 @@ from django.contrib import messages from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.template import RequestContext +from django.urls import reverse, reverse_lazy from django.utils.translation import ugettext_lazy as _ from mayan.apps.acls.models import AccessControlList +from mayan.apps.common.forms import DynamicForm from mayan.apps.common.generics import FormView, SingleObjectListView +from mayan.apps.common.mixins import ExternalObjectMixin from mayan.apps.documents.models import Document -from ..forms import WorkflowInstanceTransitionForm +from ..forms import WorkflowInstanceTransitionSelectForm from ..icons import icon_workflow_instance_detail, icon_workflow_list from ..links import link_workflow_instance_transition from ..models import WorkflowInstance @@ -18,7 +21,8 @@ from ..permissions import permission_workflow_view __all__ = ( 'DocumentWorkflowInstanceListView', 'WorkflowInstanceDetailView', - 'WorkflowInstanceTransitionView' + 'WorkflowInstanceTransitionSelectView', + 'WorkflowInstanceTransitionExecuteView' ) @@ -100,14 +104,17 @@ class WorkflowInstanceDetailView(SingleObjectListView): return get_object_or_404(klass=WorkflowInstance, pk=self.kwargs['pk']) -class WorkflowInstanceTransitionView(FormView): - form_class = WorkflowInstanceTransitionForm +class WorkflowInstanceTransitionExecuteView(FormView): + form_class = DynamicForm template_name = 'appearance/generic_form.html' def form_valid(self, form): + form_data = form.cleaned_data + comment = form_data.pop('comment') + self.get_workflow_instance().do_transition( - comment=form.cleaned_data['comment'], - transition=form.cleaned_data['transition'], user=self.request.user + comment=comment, extra_data=form_data, + transition=self.get_workflow_transition(), user=self.request.user, ) messages.success( self.request, _( @@ -122,19 +129,96 @@ class WorkflowInstanceTransitionView(FormView): 'object': self.get_workflow_instance().document, 'submit_label': _('Submit'), 'title': _( - 'Do transition for workflow: %s' - ) % self.get_workflow_instance(), + 'Execute transition "%(transition)s" for workflow: %(workflow)s' + ) % { + 'transition': self.get_workflow_transition(), + 'workflow': self.get_workflow_instance(), + }, 'workflow_instance': self.get_workflow_instance(), } def get_form_extra_kwargs(self): - return { - 'user': self.request.user, - 'workflow_instance': self.get_workflow_instance() + schema = { + 'fields': { + 'comment': { + 'label': _('Comment'), + 'class': 'django.forms.CharField', 'kwargs': { + 'help_text': _( + 'Optional comment to attach to the transition.' + ), + 'required': False, + } + } + }, + 'widgets': { + 'comment': { + 'class': 'django.forms.widgets.Textarea', + 'kwargs': { + 'attrs': { + 'rows': 3 + } + } + } + } } + for field in self.get_workflow_transition().fields.all(): + schema['fields'][field.name] = { + 'label': field.label, + 'class': 'django.forms.CharField', 'kwargs': { + } + } + + return {'schema': schema} + def get_success_url(self): return self.get_workflow_instance().get_absolute_url() def get_workflow_instance(self): - return get_object_or_404(klass=WorkflowInstance, pk=self.kwargs['pk']) + return get_object_or_404( + klass=WorkflowInstance, pk=self.kwargs['workflow_instance_pk'] + ) + + def get_workflow_transition(self): + return get_object_or_404( + klass=self.get_workflow_instance().get_transition_choices( + _user=self.request.user + ), pk=self.kwargs['workflow_transition_pk'] + ) + + +class WorkflowInstanceTransitionSelectView(ExternalObjectMixin, FormView): + external_object_class = WorkflowInstance + form_class = WorkflowInstanceTransitionSelectForm + template_name = 'appearance/generic_form.html' + + def form_valid(self, form): + return HttpResponseRedirect( + redirect_to=reverse( + viewname='document_states:workflow_instance_transition_execute', + kwargs={ + 'workflow_instance_pk': self.external_object.pk, + 'workflow_transition_pk': form.cleaned_data['transition'].pk + } + ) + ) + + def get_extra_context(self): + return { + 'navigation_object_list': ('object', 'workflow_instance'), + 'object': self.external_object.document, + 'submit_label': _('Select'), + 'title': _( + 'Select transition for workflow: %s' + ) % self.external_object, + 'workflow_instance': self.external_object, + } + + def get_form_extra_kwargs(self): + return { + 'user': self.request.user, + 'workflow_instance': self.external_object + } + + #def get_workflow_instance(self): + # return get_object_or_404(klass=WorkflowInstance, pk=self.kwargs['pk']) diff --git a/mayan/apps/document_states/views/workflow_views.py b/mayan/apps/document_states/views/workflow_views.py index 423134e0b1..b28970e932 100644 --- a/mayan/apps/document_states/views/workflow_views.py +++ b/mayan/apps/document_states/views/workflow_views.py @@ -39,7 +39,8 @@ from ..links import ( link_setup_workflow_transition_create ) from ..models import ( - Workflow, WorkflowState, WorkflowStateAction, WorkflowTransition + Workflow, WorkflowState, WorkflowStateAction, WorkflowTransition, + WorkflowTransitionField ) from ..permissions import ( permission_workflow_create, permission_workflow_delete, @@ -732,6 +733,118 @@ class SetupWorkflowTransitionTriggerEventListView(ExternalObjectMixin, FormView) ) +# Transition fields + +class SetupWorkflowTransitionFieldCreateView(ExternalObjectMixin, SingleObjectCreateView): + external_object_class = WorkflowTransition + external_object_permission = permission_workflow_edit + fields = ('name', 'label', 'help_text', 'required') + #object_permission = permission_workflow_edit + + def get_extra_context(self): + return { + 'navigation_object_list': ('transition', 'workflow'), + 'transition': self.external_object, + 'title': _( + 'Create a field for workflow transition: %s' + ) % self.external_object, + 'workflow': self.external_object.workflow + } + + def get_instance_extra_data(self): + return { + 'transition': self.external_object, + } + + def get_queryset(self): + return self.external_object.fields.all() + + def get_post_action_redirect(self): + return reverse( + viewname='document_states:setup_workflow_transition_field_list', + kwargs={'pk': self.external_object.pk} + ) + + +class SetupWorkflowTransitionFieldDeleteView(SingleObjectDeleteView): + model = WorkflowTransitionField + object_permission = permission_workflow_edit + + def get_extra_context(self): + return { + 'navigation_object_list': ( + 'object', 'workflow_transition', 'workflow' + ), + 'object': self.object, + 'title': _('Delete workflow transition field: %s') % self.object, + 'workflow': self.object.transition.workflow, + 'workflow_transition': self.object.transition, + } + + def get_post_action_redirect(self): + return reverse( + viewname='document_states:setup_workflow_transition_field_list', + kwargs={'pk': self.object.transition.pk} + ) + + +class SetupWorkflowTransitionFieldEditView(SingleObjectEditView): + fields = ('name', 'label', 'help_text', 'required',) + model = WorkflowTransitionField + object_permission = permission_workflow_edit + + def get_extra_context(self): + return { + 'navigation_object_list': ( + 'object', 'workflow_transition', 'workflow' + ), + 'object': self.object, + 'title': _('Edit workflow transition field: %s') % self.object, + 'workflow': self.object.transition.workflow, + 'workflow_transition': self.object.transition, + } + + def get_post_action_redirect(self): + return reverse( + viewname='document_states:setup_workflow_transition_field_list', + kwargs={'pk': self.object.transition.pk} + ) + + +class SetupWorkflowTransitionFieldListView(ExternalObjectMixin, SingleObjectListView): + external_object_class = WorkflowTransition + external_object_permission = permission_workflow_edit + + def get_extra_context(self): + return { + 'hide_object': True, + 'navigation_object_list': ('object', 'workflow'), + #'no_results_icon': icon_workflow_transition_action, + #'no_results_main_link': link_setup_workflow_transition_action_selection.resolve( + # context=RequestContext( + # request=self.request, dict_={ + # 'object': self.get_workflow_transition() + # } + # ) + #), + #'no_results_text': _( + # 'Workflow state actions are macros that get executed when ' + # 'documents enters or leaves the state in which they reside.' + #), + #'no_results_title': _( + # 'There are no actions for this workflow state' + #), + 'object': self.external_object, + 'title': _( + 'Fields for workflow transition: %s' + ) % self.external_object, + 'workflow': self.external_object.workflow, + } + + def get_source_queryset(self): + return self.external_object.fields.all() + + class ToolLaunchAllWorkflows(ConfirmView): extra_context = { 'title': _('Launch all workflows?'),