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 <roberto.rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-06-30 09:51:22 -04:00
parent 42db8255d1
commit e1a63064dc
9 changed files with 499 additions and 97 deletions
+32 -1
View File
@@ -40,6 +40,10 @@ from .links import (
link_setup_workflow_state_edit, link_setup_workflow_transitions, link_setup_workflow_state_edit, link_setup_workflow_transitions,
link_setup_workflow_transition_create, link_setup_workflow_transition_create,
link_setup_workflow_transition_delete, link_setup_workflow_transition_edit, 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_tool_launch_all_workflows, link_workflow_instance_detail,
link_workflow_instance_transition, link_workflow_runtime_proxy_document_list, link_workflow_instance_transition, link_workflow_runtime_proxy_document_list,
link_workflow_runtime_proxy_list, link_workflow_preview, link_workflow_runtime_proxy_list, link_workflow_preview,
@@ -86,6 +90,7 @@ class DocumentStatesApp(MayanAppConfig):
WorkflowStateAction = self.get_model('WorkflowStateAction') WorkflowStateAction = self.get_model('WorkflowStateAction')
WorkflowStateRuntimeProxy = self.get_model('WorkflowStateRuntimeProxy') WorkflowStateRuntimeProxy = self.get_model('WorkflowStateRuntimeProxy')
WorkflowTransition = self.get_model('WorkflowTransition') WorkflowTransition = self.get_model('WorkflowTransition')
WorkflowTransitionField = self.get_model('WorkflowTransitionField')
WorkflowTransitionTriggerEvent = self.get_model( WorkflowTransitionTriggerEvent = self.get_model(
'WorkflowTransitionTriggerEvent' '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( SourceColumn(
source=WorkflowRuntimeProxy, label=_('Documents'), source=WorkflowRuntimeProxy, label=_('Documents'),
func=lambda context: context['object'].get_document_count( func=lambda context: context['object'].get_document_count(
@@ -305,10 +322,18 @@ class DocumentStatesApp(MayanAppConfig):
menu_object.bind_links( menu_object.bind_links(
links=( links=(
link_setup_workflow_transition_edit, 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 link_setup_workflow_transition_delete
), sources=(WorkflowTransition,) ), 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( menu_object.bind_links(
links=( links=(
link_workflow_instance_detail, link_workflow_instance_detail,
@@ -342,6 +367,12 @@ class DocumentStatesApp(MayanAppConfig):
'document_states:setup_workflow_list' 'document_states:setup_workflow_list'
) )
) )
menu_secondary.bind_links(
links=(link_setup_workflow_transition_field_create,),
sources=(
WorkflowTransition,
)
)
menu_secondary.bind_links( menu_secondary.bind_links(
links=(link_workflow_runtime_proxy_list,), links=(link_workflow_runtime_proxy_list,),
sources=( sources=(
+2 -10
View File
@@ -165,11 +165,11 @@ WorkflowTransitionTriggerEventRelationshipFormSet = formset_factory(
) )
class WorkflowInstanceTransitionForm(forms.Form): class WorkflowInstanceTransitionSelectForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
user = kwargs.pop('user') user = kwargs.pop('user')
workflow_instance = kwargs.pop('workflow_instance') workflow_instance = kwargs.pop('workflow_instance')
super(WorkflowInstanceTransitionForm, self).__init__(*args, **kwargs) super(WorkflowInstanceTransitionSelectForm, self).__init__(*args, **kwargs)
self.fields[ self.fields[
'transition' 'transition'
].queryset = workflow_instance.get_transition_choices(_user=user) ].queryset = workflow_instance.get_transition_choices(_user=user)
@@ -177,14 +177,6 @@ class WorkflowInstanceTransitionForm(forms.Form):
transition = forms.ModelChoiceField( transition = forms.ModelChoiceField(
label=_('Transition'), queryset=WorkflowTransition.objects.none() 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): class WorkflowPreviewForm(forms.Form):
+10
View File
@@ -76,6 +76,16 @@ icon_workflow_transition_delete = Icon(driver_name='fontawesome', symbol='times'
icon_workflow_transition_edit = Icon( icon_workflow_transition_edit = Icon(
driver_name='fontawesome', symbol='pencil-alt' 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( icon_workflow_transition_triggers = Icon(
driver_name='fontawesome', symbol='bolt' driver_name='fontawesome', symbol='bolt'
) )
+30 -1
View File
@@ -129,6 +129,35 @@ link_workflow_transition_events = Link(
text=_('Transition triggers'), text=_('Transition triggers'),
view='document_states:setup_workflow_transition_events' 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( link_workflow_preview = Link(
args='resolved_object.pk', args='resolved_object.pk',
icon_class_path='mayan.apps.document_states.icons.icon_workflow_preview', icon_class_path='mayan.apps.document_states.icons.icon_workflow_preview',
@@ -159,7 +188,7 @@ link_workflow_instance_transition = Link(
args='resolved_object.pk', args='resolved_object.pk',
icon_class_path='mayan.apps.document_states.icons.icon_workflow_instance_transition', icon_class_path='mayan.apps.document_states.icons.icon_workflow_instance_transition',
text=_('Transition'), text=_('Transition'),
view='document_states:workflow_instance_transition', view='document_states:workflow_instance_transition_selection',
) )
# Runtime proxies # Runtime proxies
@@ -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')]),
),
]
+96 -31
View File
@@ -257,8 +257,8 @@ class WorkflowState(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# Solve issue #557 "Break workflows with invalid input" # Solve issue #557 "Break workflows with invalid input"
# without using a migration. # without using a migration.
# Remove blank=True, remove this, and create a migration in the next # TODO: Remove blank=True, remove this, and create a migration in the
# minor version. # next minor version.
try: try:
self.completion = int(self.completion) self.completion = int(self.completion)
@@ -363,6 +363,44 @@ class WorkflowTransition(models.Model):
return self.label 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 @python_2_unicode_compatible
class WorkflowTransitionTriggerEvent(models.Model): class WorkflowTransitionTriggerEvent(models.Model):
transition = models.ForeignKey( transition = models.ForeignKey(
@@ -392,6 +430,9 @@ class WorkflowInstance(models.Model):
on_delete=models.CASCADE, related_name='workflows', to=Document, on_delete=models.CASCADE, related_name='workflows', to=Document,
verbose_name=_('Document') verbose_name=_('Document')
) )
context = models.TextField(
blank=True, verbose_name=_('Backend data')
)
class Meta: class Meta:
ordering = ('workflow',) ordering = ('workflow',)
@@ -402,15 +443,30 @@ class WorkflowInstance(models.Model):
def __str__(self): def __str__(self):
return force_text(self.workflow) return force_text(self.workflow)
def do_transition(self, transition, user=None, comment=None): def do_transition(self, transition, extra_data=None, user=None, comment=None):
try: with transaction.atomic():
if transition in self.get_current_state().origin_transitions.all(): try:
self.log_entries.create( if transition in self.get_current_state().origin_transitions.all():
comment=comment or '', transition=transition, user=user self.log_entries.create(
) comment=comment or '', transition=transition, user=user
except AttributeError: )
# No initial state has been set for this workflow if extra_data:
pass 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): def get_absolute_url(self):
return reverse( return reverse(
@@ -420,10 +476,12 @@ class WorkflowInstance(models.Model):
) )
def get_context(self): def get_context(self):
return { context = {
'document': self.document, 'workflow': self.workflow, 'document': self.document, 'workflow': self.workflow,
'workflow_instance': self, 'workflow_instance': self,
} }
context['workflow_instance_context'] = self.loads()
return context
def get_current_state(self): def get_current_state(self):
""" """
@@ -489,6 +547,12 @@ class WorkflowInstance(models.Model):
""" """
return WorkflowTransition.objects.none() return WorkflowTransition.objects.none()
def loads(self):
"""
Deserialize the context data.
"""
return json.loads(self.context or '{}')
@python_2_unicode_compatible @python_2_unicode_compatible
class WorkflowInstanceLogEntry(models.Model): class WorkflowInstanceLogEntry(models.Model):
@@ -529,31 +593,32 @@ class WorkflowInstanceLogEntry(models.Model):
raise ValidationError(_('Not a valid transition choice.')) raise ValidationError(_('Not a valid transition choice.'))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
result = super(WorkflowInstanceLogEntry, self).save(*args, **kwargs) with transaction.atomic():
context = self.workflow_instance.get_context() result = super(WorkflowInstanceLogEntry, self).save(*args, **kwargs)
context.update( context = self.workflow_instance.get_context()
{
'entry_log': self
}
)
for action in self.transition.origin_state.exit_actions.filter(enabled=True):
context.update( context.update(
{ {
'action': action, 'entry_log': self
} }
) )
action.execute(context=context)
for action in self.transition.destination_state.entry_actions.filter(enabled=True): for action in self.transition.origin_state.exit_actions.filter(enabled=True):
context.update( context.update(
{ {
'action': action, 'action': action,
} }
) )
action.execute(context=context) 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): class WorkflowRuntimeProxy(Workflow):
+79 -41
View File
@@ -22,19 +22,86 @@ from .views import (
SetupWorkflowTransitionEditView, SetupWorkflowTransitionEditView,
SetupWorkflowTransitionTriggerEventListView, ToolLaunchAllWorkflows, SetupWorkflowTransitionTriggerEventListView, ToolLaunchAllWorkflows,
WorkflowDocumentListView, WorkflowInstanceDetailView, WorkflowDocumentListView, WorkflowInstanceDetailView,
WorkflowImageView, WorkflowInstanceTransitionView, WorkflowListView, WorkflowImageView, WorkflowInstanceTransitionExecuteView,
WorkflowInstanceTransitionSelectView, WorkflowListView,
WorkflowPreviewView, WorkflowStateDocumentListView, WorkflowStateListView, WorkflowPreviewView, WorkflowStateDocumentListView, WorkflowStateListView,
) )
from .views.workflow_views import SetupDocumentTypeWorkflowsView from .views.workflow_views import (
SetupDocumentTypeWorkflowsView, SetupWorkflowTransitionFieldCreateView,
SetupWorkflowTransitionFieldDeleteView,
SetupWorkflowTransitionFieldEditView, SetupWorkflowTransitionFieldListView
)
urlpatterns_workflows = [ urlpatterns_workflows = [
url( url(
regex=r'^document_type/(?P<pk>\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<pk>\d+)/delete/$',
view=SetupWorkflowDeleteView.as_view(), name='setup_workflow_delete'
),
url(
regex=r'^setup/workflows/(?P<pk>\d+)/edit/$',
view=SetupWorkflowEditView.as_view(), name='setup_workflow_edit'
),
url(
regex=r'^setup/document_types/(?P<pk>\d+)/workflows/$',
view=SetupDocumentTypeWorkflowsView.as_view(), view=SetupDocumentTypeWorkflowsView.as_view(),
name='document_type_workflows' name='document_type_workflows'
), ),
] ]
urlpatterns_workflow_states = [
url(
regex=r'^setup/workflow/(?P<pk>\d+)/states/$',
view=SetupWorkflowStateListView.as_view(),
name='setup_workflow_state_list'
),
url(
regex=r'^setup/workflow/(?P<pk>\d+)/states/create/$',
view=SetupWorkflowStateCreateView.as_view(),
name='setup_workflow_state_create'
),
url(
regex=r'^setup/workflow/state/(?P<pk>\d+)/delete/$',
view=SetupWorkflowStateDeleteView.as_view(),
name='setup_workflow_state_delete'
),
url(
regex=r'^setup/workflow/state/(?P<pk>\d+)/edit/$',
view=SetupWorkflowStateEditView.as_view(),
name='setup_workflow_state_edit'
),
]
urlpatterns_workflow_transition_fields = [
url(
regex=r'^setup/workflows/transitions/(?P<pk>\d+)/fields/create/$',
view=SetupWorkflowTransitionFieldCreateView.as_view(),
name='setup_workflow_transition_field_create'
),
url(
regex=r'^setup/workflows/transitions/(?P<pk>\d+)/fields/$',
view=SetupWorkflowTransitionFieldListView.as_view(),
name='setup_workflow_transition_field_list'
),
url(
regex=r'^setup/workflows/transitions/fields/(?P<pk>\d+)/delete/$',
view=SetupWorkflowTransitionFieldDeleteView.as_view(),
name='setup_workflow_transition_field_delete'
),
url(
regex=r'^setup/workflows/transitions/fields/(?P<pk>\d+)/edit/$',
view=SetupWorkflowTransitionFieldEditView.as_view(),
name='setup_workflow_transition_field_edit'
),
]
urlpatterns = [ urlpatterns = [
url( url(
regex=r'^document/(?P<pk>\d+)/workflows/$', regex=r'^document/(?P<pk>\d+)/workflows/$',
@@ -47,25 +114,14 @@ urlpatterns = [
name='workflow_instance_detail' name='workflow_instance_detail'
), ),
url( url(
regex=r'^document/workflows/(?P<pk>\d+)/transition/$', regex=r'^document/workflows/(?P<pk>\d+)/transitions/select/$',
view=WorkflowInstanceTransitionView.as_view(), view=WorkflowInstanceTransitionSelectView.as_view(),
name='workflow_instance_transition' name='workflow_instance_transition_selection'
), ),
url( url(
regex=r'^setup/all/$', view=SetupWorkflowListView.as_view(), regex=r'^document/workflows/(?P<workflow_instance_pk>\d+)/transitions/(?P<workflow_transition_pk>\d+)/execute/$',
name='setup_workflow_list' view=WorkflowInstanceTransitionExecuteView.as_view(),
), name='workflow_instance_transition_execute'
url(
regex=r'^setup/create/$', view=SetupWorkflowCreateView.as_view(),
name='setup_workflow_create'
),
url(
regex=r'^setup/workflow/(?P<pk>\d+)/edit/$',
view=SetupWorkflowEditView.as_view(), name='setup_workflow_edit'
),
url(
regex=r'^setup/workflow/(?P<pk>\d+)/delete/$',
view=SetupWorkflowDeleteView.as_view(), name='setup_workflow_delete'
), ),
url( url(
regex=r'^setup/workflow/(?P<pk>\d+)/documents/$', regex=r'^setup/workflow/(?P<pk>\d+)/documents/$',
@@ -77,16 +133,6 @@ urlpatterns = [
view=SetupWorkflowDocumentTypesView.as_view(), view=SetupWorkflowDocumentTypesView.as_view(),
name='setup_workflow_document_types' name='setup_workflow_document_types'
), ),
url(
regex=r'^setup/workflow/(?P<pk>\d+)/states/$',
view=SetupWorkflowStateListView.as_view(),
name='setup_workflow_state_list'
),
url(
regex=r'^setup/workflow/(?P<pk>\d+)/states/create/$',
view=SetupWorkflowStateCreateView.as_view(),
name='setup_workflow_state_create'
),
url( url(
regex=r'^setup/workflow/(?P<pk>\d+)/transitions/$', regex=r'^setup/workflow/(?P<pk>\d+)/transitions/$',
view=SetupWorkflowTransitionListView.as_view(), view=SetupWorkflowTransitionListView.as_view(),
@@ -98,20 +144,10 @@ urlpatterns = [
name='setup_workflow_transition_create' name='setup_workflow_transition_create'
), ),
url( url(
regex=r'^setup/workflow/(?P<pk>\d+)/transitions/events/$', regex=r'^setup/workflow/transitions/(?P<pk>\d+)/events/$',
view=SetupWorkflowTransitionTriggerEventListView.as_view(), view=SetupWorkflowTransitionTriggerEventListView.as_view(),
name='setup_workflow_transition_events' name='setup_workflow_transition_events'
), ),
url(
regex=r'^setup/workflow/state/(?P<pk>\d+)/delete/$',
view=SetupWorkflowStateDeleteView.as_view(),
name='setup_workflow_state_delete'
),
url(
regex=r'^setup/workflow/state/(?P<pk>\d+)/edit/$',
view=SetupWorkflowStateEditView.as_view(),
name='setup_workflow_state_edit'
),
url( url(
regex=r'^setup/workflow/state/(?P<pk>\d+)/actions/$', regex=r'^setup/workflow/state/(?P<pk>\d+)/actions/$',
view=SetupWorkflowStateActionListView.as_view(), view=SetupWorkflowStateActionListView.as_view(),
@@ -184,6 +220,8 @@ urlpatterns = [
), ),
] ]
urlpatterns.extend(urlpatterns_workflows) urlpatterns.extend(urlpatterns_workflows)
urlpatterns.extend(urlpatterns_workflow_states)
urlpatterns.extend(urlpatterns_workflow_transition_fields)
api_urls = [ api_urls = [
url( url(
@@ -4,13 +4,16 @@ from django.contrib import messages
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template import RequestContext from django.template import RequestContext
from django.urls import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _ 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.forms import DynamicForm
from mayan.apps.common.generics import FormView, SingleObjectListView from mayan.apps.common.generics import FormView, SingleObjectListView
from mayan.apps.common.mixins import ExternalObjectMixin
from mayan.apps.documents.models import Document 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 ..icons import icon_workflow_instance_detail, icon_workflow_list
from ..links import link_workflow_instance_transition from ..links import link_workflow_instance_transition
from ..models import WorkflowInstance from ..models import WorkflowInstance
@@ -18,7 +21,8 @@ from ..permissions import permission_workflow_view
__all__ = ( __all__ = (
'DocumentWorkflowInstanceListView', 'WorkflowInstanceDetailView', 'DocumentWorkflowInstanceListView', 'WorkflowInstanceDetailView',
'WorkflowInstanceTransitionView' 'WorkflowInstanceTransitionSelectView',
'WorkflowInstanceTransitionExecuteView'
) )
@@ -100,14 +104,17 @@ class WorkflowInstanceDetailView(SingleObjectListView):
return get_object_or_404(klass=WorkflowInstance, pk=self.kwargs['pk']) return get_object_or_404(klass=WorkflowInstance, pk=self.kwargs['pk'])
class WorkflowInstanceTransitionView(FormView): class WorkflowInstanceTransitionExecuteView(FormView):
form_class = WorkflowInstanceTransitionForm form_class = DynamicForm
template_name = 'appearance/generic_form.html' template_name = 'appearance/generic_form.html'
def form_valid(self, form): def form_valid(self, form):
form_data = form.cleaned_data
comment = form_data.pop('comment')
self.get_workflow_instance().do_transition( self.get_workflow_instance().do_transition(
comment=form.cleaned_data['comment'], comment=comment, extra_data=form_data,
transition=form.cleaned_data['transition'], user=self.request.user transition=self.get_workflow_transition(), user=self.request.user,
) )
messages.success( messages.success(
self.request, _( self.request, _(
@@ -122,19 +129,96 @@ class WorkflowInstanceTransitionView(FormView):
'object': self.get_workflow_instance().document, 'object': self.get_workflow_instance().document,
'submit_label': _('Submit'), 'submit_label': _('Submit'),
'title': _( 'title': _(
'Do transition for workflow: %s' 'Execute transition "%(transition)s" for workflow: %(workflow)s'
) % self.get_workflow_instance(), ) % {
'transition': self.get_workflow_transition(),
'workflow': self.get_workflow_instance(),
},
'workflow_instance': self.get_workflow_instance(), 'workflow_instance': self.get_workflow_instance(),
} }
def get_form_extra_kwargs(self): def get_form_extra_kwargs(self):
return { schema = {
'user': self.request.user, 'fields': {
'workflow_instance': self.get_workflow_instance() '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): def get_success_url(self):
return self.get_workflow_instance().get_absolute_url() return self.get_workflow_instance().get_absolute_url()
def get_workflow_instance(self): 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'])
@@ -39,7 +39,8 @@ from ..links import (
link_setup_workflow_transition_create link_setup_workflow_transition_create
) )
from ..models import ( from ..models import (
Workflow, WorkflowState, WorkflowStateAction, WorkflowTransition Workflow, WorkflowState, WorkflowStateAction, WorkflowTransition,
WorkflowTransitionField
) )
from ..permissions import ( from ..permissions import (
permission_workflow_create, permission_workflow_delete, 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): class ToolLaunchAllWorkflows(ConfirmView):
extra_context = { extra_context = {
'title': _('Launch all workflows?'), 'title': _('Launch all workflows?'),