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:
@@ -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=(
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')]),
|
||||
),
|
||||
]
|
||||
@@ -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):
|
||||
|
||||
@@ -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<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(),
|
||||
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 = [
|
||||
url(
|
||||
regex=r'^document/(?P<pk>\d+)/workflows/$',
|
||||
@@ -47,25 +114,14 @@ urlpatterns = [
|
||||
name='workflow_instance_detail'
|
||||
),
|
||||
url(
|
||||
regex=r'^document/workflows/(?P<pk>\d+)/transition/$',
|
||||
view=WorkflowInstanceTransitionView.as_view(),
|
||||
name='workflow_instance_transition'
|
||||
regex=r'^document/workflows/(?P<pk>\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<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'
|
||||
regex=r'^document/workflows/(?P<workflow_instance_pk>\d+)/transitions/(?P<workflow_transition_pk>\d+)/execute/$',
|
||||
view=WorkflowInstanceTransitionExecuteView.as_view(),
|
||||
name='workflow_instance_transition_execute'
|
||||
),
|
||||
url(
|
||||
regex=r'^setup/workflow/(?P<pk>\d+)/documents/$',
|
||||
@@ -77,16 +133,6 @@ urlpatterns = [
|
||||
view=SetupWorkflowDocumentTypesView.as_view(),
|
||||
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(
|
||||
regex=r'^setup/workflow/(?P<pk>\d+)/transitions/$',
|
||||
view=SetupWorkflowTransitionListView.as_view(),
|
||||
@@ -98,20 +144,10 @@ urlpatterns = [
|
||||
name='setup_workflow_transition_create'
|
||||
),
|
||||
url(
|
||||
regex=r'^setup/workflow/(?P<pk>\d+)/transitions/events/$',
|
||||
regex=r'^setup/workflow/transitions/(?P<pk>\d+)/events/$',
|
||||
view=SetupWorkflowTransitionTriggerEventListView.as_view(),
|
||||
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(
|
||||
regex=r'^setup/workflow/state/(?P<pk>\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(
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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?'),
|
||||
|
||||
Reference in New Issue
Block a user