diff --git a/HISTORY.rst b/HISTORY.rst
index 0fb21226a7..d14468108c 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -8,6 +8,8 @@
- Backport workflow preview refactor. GitLab issue #532.
- Add support for source column inheritance.
- Add support for source column exclusion.
+- Backport workflow context support.
+- Backport workflow transitions field support.
3.2.5 (2019-07-05)
==================
diff --git a/docs/releases/3.3.rst b/docs/releases/3.3.rst
index 980763c0a0..6a66ffeb19 100644
--- a/docs/releases/3.3.rst
+++ b/docs/releases/3.3.rst
@@ -20,6 +20,8 @@ Changes
- Backport workflow preview refactor. GitLab issue #532.
- Add support for source column inheritance.
- Add support for source column exclusion.
+- Backport workflow context support.
+- Backport workflow transitions field support.
Removals
--------
diff --git a/mayan/apps/document_states/apps.py b/mayan/apps/document_states/apps.py
index f49310b836..0e35c0d59c 100644
--- a/mayan/apps/document_states/apps.py
+++ b/mayan/apps/document_states/apps.py
@@ -27,6 +27,7 @@ from .dependencies import * # NOQA
from .handlers import (
handler_index_document, handler_launch_workflow, handler_trigger_transition
)
+from .html_widgets import WorkflowLogExtraDataWidget, widget_transition_events
from .links import (
link_document_workflow_instance_list, link_setup_document_type_workflows,
link_setup_workflow_document_types, link_setup_workflow_create,
@@ -40,6 +41,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,
@@ -50,7 +55,6 @@ from .permissions import (
permission_workflow_delete, permission_workflow_edit,
permission_workflow_transition, permission_workflow_view
)
-from .widgets import widget_transition_events
class DocumentStatesApp(MayanAppConfig):
@@ -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'
)
@@ -152,6 +157,9 @@ class DocumentStatesApp(MayanAppConfig):
ModelPermission.register_inheritance(
model=WorkflowTransition, related='workflow',
)
+ ModelPermission.register_inheritance(
+ model=WorkflowTransitionField, related='transition',
+ )
ModelPermission.register_inheritance(
model=WorkflowTransitionTriggerEvent,
related='transition__workflow',
@@ -160,9 +168,10 @@ class DocumentStatesApp(MayanAppConfig):
SourceColumn(
attribute='label', is_sortable=True, source=Workflow
)
- SourceColumn(
+ column_workflow_internal_name = SourceColumn(
attribute='internal_name', is_sortable=True, source=Workflow
)
+ column_workflow_internal_name.add_exclude(source=WorkflowRuntimeProxy)
SourceColumn(
attribute='get_initial_state', empty_value=_('None'),
source=Workflow
@@ -203,12 +212,25 @@ class DocumentStatesApp(MayanAppConfig):
source=WorkflowInstanceLogEntry, label=_('User'), attribute='user'
)
SourceColumn(
- source=WorkflowInstanceLogEntry, label=_('Transition'),
- attribute='transition'
+ source=WorkflowInstanceLogEntry,
+ attribute='transition__origin_state', is_sortable=True
)
SourceColumn(
- source=WorkflowInstanceLogEntry, label=_('Comment'),
- attribute='comment'
+ source=WorkflowInstanceLogEntry,
+ attribute='transition', is_sortable=True
+ )
+ SourceColumn(
+ source=WorkflowInstanceLogEntry,
+ attribute='transition__destination_state', is_sortable=True
+ )
+ SourceColumn(
+ source=WorkflowInstanceLogEntry,
+ attribute='comment', is_sortable=True
+ )
+ SourceColumn(
+ source=WorkflowInstanceLogEntry,
+ attribute='get_extra_data', label=_('Additional details'),
+ widget=WorkflowLogExtraDataWidget
)
SourceColumn(
@@ -256,6 +278,43 @@ class DocumentStatesApp(MayanAppConfig):
)
)
+ SourceColumn(
+ attribute='name', is_identifier=True, is_sortable=True,
+ source=WorkflowTransitionField
+ )
+ SourceColumn(
+ attribute='label', is_sortable=True, source=WorkflowTransitionField
+ )
+ SourceColumn(
+ attribute='get_field_type_display', label=_('Type'),
+ source=WorkflowTransitionField
+ )
+ SourceColumn(
+ attribute='required', is_sortable=True,
+ source=WorkflowTransitionField, widget=TwoStateWidget
+ )
+ SourceColumn(
+ attribute='get_widget_display', label=_('Widget'),
+ is_sortable=False, source=WorkflowTransitionField
+ )
+ SourceColumn(
+ attribute='widget_kwargs', is_sortable=True,
+ source=WorkflowTransitionField
+ )
+
+ SourceColumn(
+ source=WorkflowRuntimeProxy, label=_('Documents'),
+ func=lambda context: context['object'].get_document_count(
+ user=context['request'].user
+ ), order=99
+ )
+ SourceColumn(
+ source=WorkflowStateRuntimeProxy, label=_('Documents'),
+ func=lambda context: context['object'].get_document_count(
+ user=context['request'].user
+ ), order=99
+ )
+
menu_facet.bind_links(
links=(link_document_workflow_instance_list,), sources=(Document,)
)
@@ -291,10 +350,17 @@ 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,
@@ -328,6 +394,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 930dbf49e9..e2320e2b46 100644
--- a/mayan/apps/document_states/forms.py
+++ b/mayan/apps/document_states/forms.py
@@ -165,26 +165,19 @@ 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)
transition = forms.ModelChoiceField(
+ help_text=_('Select a transition to execute in the next step.'),
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/html_widgets.py b/mayan/apps/document_states/html_widgets.py
new file mode 100644
index 0000000000..98ad2d817f
--- /dev/null
+++ b/mayan/apps/document_states/html_widgets.py
@@ -0,0 +1,27 @@
+from __future__ import unicode_literals
+
+from django import forms
+from django.template.loader import render_to_string
+from django.urls import reverse
+from django.utils.html import format_html_join, mark_safe
+
+
+def widget_transition_events(transition):
+ return format_html_join(
+ sep='\n', format_string='
{}
', args_generator=(
+ (
+ transition_trigger.event_type.label,
+ ) for transition_trigger in transition.trigger_events.all()
+ )
+ )
+
+
+class WorkflowLogExtraDataWidget(object):
+ template_name = 'document_states/extra_data.html'
+
+ def render(self, name=None, value=None):
+ return render_to_string(
+ template_name=self.template_name, context={
+ 'value': value
+ }
+ )
diff --git a/mayan/apps/document_states/icons.py b/mayan/apps/document_states/icons.py
index 8ae3b8e990..9fa519fac1 100644
--- a/mayan/apps/document_states/icons.py
+++ b/mayan/apps/document_states/icons.py
@@ -3,7 +3,6 @@ from __future__ import absolute_import, unicode_literals
from mayan.apps.appearance.classes import Icon
from mayan.apps.documents.icons import icon_document_type
-
icon_workflow = Icon(driver_name='fontawesome', symbol='sitemap')
icon_document_type_workflow_list = icon_workflow
@@ -25,8 +24,9 @@ icon_workflow_edit = Icon(driver_name='fontawesome', symbol='pencil-alt')
icon_workflow_list = Icon(driver_name='fontawesome', symbol='sitemap')
icon_workflow_preview = Icon(driver_name='fontawesome', symbol='eye')
-
-icon_workflow_instance_detail = Icon(driver_name='fontawesome', symbol='sitemap')
+icon_workflow_instance_detail = Icon(
+ driver_name='fontawesome', symbol='sitemap'
+)
icon_workflow_instance_transition = Icon(
driver_name='fontawesome', symbol='arrows-alt-h'
)
@@ -58,13 +58,19 @@ icon_workflow_state_delete = Icon(driver_name='fontawesome', symbol='times')
icon_workflow_state_edit = Icon(driver_name='fontawesome', symbol='pencil-alt')
icon_workflow_state_action = Icon(driver_name='fontawesome', symbol='code')
-icon_workflow_state_action_delete = Icon(driver_name='fontawesome', symbol='times')
-icon_workflow_state_action_edit = Icon(driver_name='fontawesome', symbol='pencil-alt')
+icon_workflow_state_action_delete = Icon(
+ driver_name='fontawesome', symbol='times'
+)
+icon_workflow_state_action_edit = Icon(
+ driver_name='fontawesome', symbol='pencil-alt'
+)
icon_workflow_state_action_selection = Icon(
driver_name='fontawesome-dual', primary_symbol='code',
secondary_symbol='plus'
)
-icon_workflow_state_action_list = Icon(driver_name='fontawesome', symbol='code')
+icon_workflow_state_action_list = Icon(
+ driver_name='fontawesome', symbol='code'
+)
icon_workflow_transition = Icon(
driver_name='fontawesome', symbol='arrows-alt-h'
)
@@ -72,10 +78,28 @@ icon_workflow_transition_create = Icon(
driver_name='fontawesome-dual', primary_symbol='arrows-alt-h',
secondary_symbol='plus'
)
-icon_workflow_transition_delete = Icon(driver_name='fontawesome', symbol='times')
+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='table')
+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='table',
+ secondary_symbol='plus'
+)
+icon_workflow_transition_field_list = Icon(
+ driver_name='fontawesome', symbol='table'
+)
+
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/literals.py b/mayan/apps/document_states/literals.py
index 674bbeebef..ad6906fd3b 100644
--- a/mayan/apps/document_states/literals.py
+++ b/mayan/apps/document_states/literals.py
@@ -2,6 +2,27 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
+FIELD_TYPE_CHOICE_CHAR = 1
+FIELD_TYPE_CHOICE_INTEGER = 2
+FIELD_TYPE_CHOICES = (
+ (FIELD_TYPE_CHOICE_CHAR, _('Character')),
+ (FIELD_TYPE_CHOICE_INTEGER, _('Number (Integer)')),
+)
+
+FIELD_TYPE_MAPPING = {
+ FIELD_TYPE_CHOICE_CHAR: 'django.forms.CharField',
+ FIELD_TYPE_CHOICE_INTEGER: 'django.forms.IntegerField',
+}
+
+WIDGET_CLASS_TEXTAREA = 1
+WIDGET_CLASS_CHOICES = (
+ (WIDGET_CLASS_TEXTAREA, _('Text area')),
+)
+
+WIDGET_CLASS_MAPPING = {
+ WIDGET_CLASS_TEXTAREA: 'django.forms.widgets.Textarea',
+}
+
WORKFLOW_ACTION_ON_ENTRY = 1
WORKFLOW_ACTION_ON_EXIT = 2
diff --git a/mayan/apps/document_states/migrations/0014_auto_20190701_0454.py b/mayan/apps/document_states/migrations/0014_auto_20190701_0454.py
new file mode 100644
index 0000000000..6ede77fcd0
--- /dev/null
+++ b/mayan/apps/document_states/migrations/0014_auto_20190701_0454.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-07-01 04:54
+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')),
+ ('field_type', models.PositiveIntegerField(choices=[(1, 'Character'), (2, 'Number (Integer)')], verbose_name='Type')),
+ ('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')),
+ ('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.AddField(
+ model_name='workflowinstancelogentry',
+ name='extra_data',
+ field=models.TextField(blank=True, verbose_name='Extra data'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='workflowtransitionfield',
+ unique_together=set([('transition', 'name')]),
+ ),
+ ]
diff --git a/mayan/apps/document_states/migrations/0015_auto_20190701_1311.py b/mayan/apps/document_states/migrations/0015_auto_20190701_1311.py
new file mode 100644
index 0000000000..baef5fc886
--- /dev/null
+++ b/mayan/apps/document_states/migrations/0015_auto_20190701_1311.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-07-01 13:11
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import mayan.apps.common.validators
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('document_states', '0014_auto_20190701_0454'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='workflowtransitionfield',
+ name='widget',
+ field=models.PositiveIntegerField(blank=True, choices=[(1, 'Text area')], help_text='An optional class to change the default presentation of the field.', null=True, verbose_name='Widget class'),
+ ),
+ migrations.AddField(
+ model_name='workflowtransitionfield',
+ name='widget_kwargs',
+ field=models.TextField(blank=True, help_text='A group of keyword arguments to customize the widget. Use YAML format.', validators=[mayan.apps.common.validators.YAMLValidator()], verbose_name='Widget keyword arguments'),
+ ),
+ migrations.AlterField(
+ model_name='workflowinstance',
+ name='context',
+ field=models.TextField(blank=True, verbose_name='Context'),
+ ),
+ ]
diff --git a/mayan/apps/document_states/models.py b/mayan/apps/document_states/models.py
index edfa431691..d3d4103814 100644
--- a/mayan/apps/document_states/models.py
+++ b/mayan/apps/document_states/models.py
@@ -6,6 +6,11 @@ import logging
from furl import furl
from graphviz import Digraph
+import yaml
+try:
+ from yaml import CSafeLoader as SafeLoader
+except ImportError:
+ from yaml import SafeLoader
from django.conf import settings
from django.core import serializers
@@ -19,15 +24,16 @@ from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.models import AccessControlList
-from mayan.apps.common.validators import validate_internal_name
+from mayan.apps.common.validators import YAMLValidator, validate_internal_name
from mayan.apps.documents.models import Document, DocumentType
+from mayan.apps.documents.permissions import permission_document_view
from mayan.apps.events.models import StoredEventType
from .error_logs import error_log_state_actions
from .events import event_workflow_created, event_workflow_edited
from .literals import (
- WORKFLOW_ACTION_WHEN_CHOICES, WORKFLOW_ACTION_ON_ENTRY,
- WORKFLOW_ACTION_ON_EXIT
+ FIELD_TYPE_CHOICES, WIDGET_CLASS_CHOICES, WORKFLOW_ACTION_WHEN_CHOICES,
+ WORKFLOW_ACTION_ON_ENTRY, WORKFLOW_ACTION_ON_EXIT
)
from .managers import WorkflowManager
from .permissions import permission_workflow_transition
@@ -407,6 +413,61 @@ 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')
+ )
+ field_type = models.PositiveIntegerField(
+ choices=FIELD_TYPE_CHOICES, verbose_name=_('Type')
+ )
+ 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')
+ )
+ widget = models.PositiveIntegerField(
+ blank=True, choices=WIDGET_CLASS_CHOICES, help_text=_(
+ 'An optional class to change the default presentation of the field.'
+ ), null=True, verbose_name=_('Widget class')
+ )
+ widget_kwargs = models.TextField(
+ blank=True, help_text=_(
+ 'A group of keyword arguments to customize the widget. '
+ 'Use YAML format.'
+ ), validators=[YAMLValidator()],
+ verbose_name=_('Widget keyword arguments')
+ )
+
+ 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
+
+ def get_widget_kwargs(self):
+ return yaml.load(stream=self.widget_kwargs, Loader=SafeLoader)
+
+
@python_2_unicode_compatible
class WorkflowTransitionTriggerEvent(models.Model):
transition = models.ForeignKey(
@@ -436,6 +497,9 @@ class WorkflowInstance(models.Model):
on_delete=models.CASCADE, related_name='workflows', to=Document,
verbose_name=_('Document')
)
+ context = models.TextField(
+ blank=True, verbose_name=_('Context')
+ )
class Meta:
ordering = ('workflow',)
@@ -446,15 +510,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():
+ if extra_data:
+ context = self.loads()
+ context.update(extra_data)
+ self.dumps(context=context)
+
+ self.log_entries.create(
+ comment=comment or '',
+ extra_data=json.dumps(extra_data or {}),
+ transition=transition, user=user
+ )
+ except AttributeError:
+ # No initial state has been set for this workflow
+ pass
+
+ def dumps(self, context):
+ """
+ Serialize the context data.
+ """
+ self.context = json.dumps(context)
+ self.save()
def get_absolute_url(self):
return reverse(
@@ -464,10 +543,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):
"""
@@ -533,6 +614,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):
@@ -559,6 +646,7 @@ class WorkflowInstanceLogEntry(models.Model):
to=settings.AUTH_USER_MODEL, verbose_name=_('User')
)
comment = models.TextField(blank=True, verbose_name=_('Comment'))
+ extra_data = models.TextField(blank=True, verbose_name=_('Extra data'))
class Meta:
ordering = ('datetime',)
@@ -572,33 +660,47 @@ class WorkflowInstanceLogEntry(models.Model):
if self.transition not in self.workflow_instance.get_transition_choices(_user=self.user):
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):
- context.update(
- {
- 'action': action,
- }
- )
- action.execute(context=context)
-
- for action in self.transition.destination_state.entry_actions.filter(enabled=True):
- context.update(
- {
- 'action': action,
- }
- )
- action.execute(context=context)
+ def get_extra_data(self):
+ result = {}
+ for key, value in self.loads().items():
+ result[self.transition.fields.get(name=key).label] = value
return result
+ def loads(self):
+ """
+ Deserialize the context data.
+ """
+ return json.loads(self.extra_data or '{}')
+
+ def save(self, *args, **kwargs):
+ with transaction.atomic():
+ 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):
+ context.update(
+ {
+ 'action': action,
+ }
+ )
+ action.execute(context=context)
+
+ 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 Meta:
@@ -606,9 +708,30 @@ class WorkflowRuntimeProxy(Workflow):
verbose_name = _('Workflow runtime proxy')
verbose_name_plural = _('Workflow runtime proxies')
+ def get_document_count(self, user):
+ """
+ Return the numeric count of documents executing this workflow.
+ The count is filtered by access.
+ """
+ return AccessControlList.objects.restrict_queryset(
+ permission=permission_document_view,
+ queryset=Document.objects.filter(workflows__workflow=self),
+ user=user
+ ).count()
+
class WorkflowStateRuntimeProxy(WorkflowState):
class Meta:
proxy = True
verbose_name = _('Workflow state runtime proxy')
verbose_name_plural = _('Workflow state runtime proxies')
+
+ def get_document_count(self, user):
+ """
+ Return the numeric count of documents at this workflow state.
+ The count is filtered by access.
+ """
+ return AccessControlList.objects.restrict_queryset(
+ permission=permission_document_view, queryset=self.get_documents(),
+ user=user
+ ).count()
diff --git a/mayan/apps/document_states/templates/document_states/extra_data.html b/mayan/apps/document_states/templates/document_states/extra_data.html
new file mode 100644
index 0000000000..b996601769
--- /dev/null
+++ b/mayan/apps/document_states/templates/document_states/extra_data.html
@@ -0,0 +1,8 @@
+{% if value %}
+
+ {% for key, value in value.items %}
+ - {{ key }}: {{ value }}
+ {% endfor %}
+
+{% endif %}
+
diff --git a/mayan/apps/document_states/tests/literals.py b/mayan/apps/document_states/tests/literals.py
index 212427d2d3..c17a870e9c 100644
--- a/mayan/apps/document_states/tests/literals.py
+++ b/mayan/apps/document_states/tests/literals.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
+from ..literals import FIELD_TYPE_CHOICE_CHAR
+
TEST_INDEX_LABEL = 'test workflow index'
TEST_WORKFLOW_LABEL = 'test workflow label'
@@ -11,6 +13,10 @@ TEST_WORKFLOW_INSTANCE_LOG_ENTRY_COMMENT = 'test workflow instance log entry com
TEST_WORKFLOW_STATE_LABEL = 'test state label'
TEST_WORKFLOW_STATE_LABEL_EDITED = 'test state label edited'
TEST_WORKFLOW_STATE_COMPLETION = 66
+TEST_WORKFLOW_TRANSITION_FIELD_HELP_TEXT = 'test workflow transition field help test'
+TEST_WORKFLOW_TRANSITION_FIELD_LABEL = 'test workflow transition field'
+TEST_WORKFLOW_TRANSITION_FIELD_NAME = 'test_workflow_transition_field'
+TEST_WORKFLOW_TRANSITION_FIELD_TYPE = FIELD_TYPE_CHOICE_CHAR
TEST_WORKFLOW_TRANSITION_LABEL = 'test transition label'
TEST_WORKFLOW_TRANSITION_LABEL_2 = 'test transition label 2'
TEST_WORKFLOW_TRANSITION_LABEL_EDITED = 'test transition label edited'
@@ -18,3 +24,4 @@ TEST_WORKFLOW_TRANSITION_LABEL_EDITED = 'test transition label edited'
TEST_INDEX_TEMPLATE_METADATA_EXPRESSION = '{{{{ document.workflow.{}.get_current_state }}}}'.format(
TEST_WORKFLOW_INTERNAL_NAME
)
+
diff --git a/mayan/apps/document_states/tests/mixins.py b/mayan/apps/document_states/tests/mixins.py
index 76a1865ed4..f8ea985a1c 100644
--- a/mayan/apps/document_states/tests/mixins.py
+++ b/mayan/apps/document_states/tests/mixins.py
@@ -152,9 +152,10 @@ class WorkflowTransitionViewTestMixin(object):
def _request_test_workflow_transition(self):
return self.post(
- viewname='document_states:workflow_instance_transition',
- kwargs={'pk': self.test_workflow_instance.pk}, data={
- 'transition': self.test_workflow_transition.pk,
+ viewname='document_states:workflow_instance_transition_execute',
+ kwargs={
+ 'workflow_instance_pk': self.test_workflow_instance.pk,
+ 'workflow_transition_pk': self.test_workflow_transition.pk,
}
)
diff --git a/mayan/apps/document_states/tests/test_workflow_transition_views.py b/mayan/apps/document_states/tests/test_workflow_transition_views.py
index 1eb8dfb133..c298e5dde4 100644
--- a/mayan/apps/document_states/tests/test_workflow_transition_views.py
+++ b/mayan/apps/document_states/tests/test_workflow_transition_views.py
@@ -10,7 +10,10 @@ from ..permissions import (
)
from .literals import (
- TEST_WORKFLOW_TRANSITION_LABEL, TEST_WORKFLOW_TRANSITION_LABEL_EDITED
+ TEST_WORKFLOW_TRANSITION_FIELD_HELP_TEXT,
+ TEST_WORKFLOW_TRANSITION_FIELD_LABEL, TEST_WORKFLOW_TRANSITION_FIELD_NAME,
+ TEST_WORKFLOW_TRANSITION_FIELD_TYPE, TEST_WORKFLOW_TRANSITION_LABEL,
+ TEST_WORKFLOW_TRANSITION_LABEL_EDITED
)
from .mixins import (
WorkflowTestMixin, WorkflowViewTestMixin, WorkflowTransitionViewTestMixin
@@ -160,7 +163,7 @@ class WorkflowTransitionDocumentViewTestCase(
permission.
"""
response = self._request_test_workflow_transition()
- self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.status_code, 404)
# Workflow should remain in the same initial state
self.assertEqual(
@@ -232,3 +235,125 @@ class WorkflowTransitionEventViewTestCase(
response = self._request_test_workflow_transition_event_list_view()
self.assertEqual(response.status_code, 200)
+
+
+class WorkflowTransitionFieldViewTestCase(
+ WorkflowTestMixin, WorkflowTransitionViewTestMixin, GenericViewTestCase
+):
+ def setUp(self):
+ super(WorkflowTransitionFieldViewTestCase, self).setUp()
+ self._create_test_workflow()
+ self._create_test_workflow_states()
+ self._create_test_workflow_transition()
+
+ def _create_test_workflow_transition_field(self):
+ self.test_workflow_transition_field = self.test_workflow_transition.fields.create(
+ field_type=TEST_WORKFLOW_TRANSITION_FIELD_TYPE,
+ name=TEST_WORKFLOW_TRANSITION_FIELD_NAME,
+ label=TEST_WORKFLOW_TRANSITION_FIELD_LABEL,
+ help_text=TEST_WORKFLOW_TRANSITION_FIELD_HELP_TEXT
+ )
+
+ def _request_test_workflow_transition_field_list_view(self):
+ return self.get(
+ viewname='document_states:setup_workflow_transition_field_list',
+ kwargs={'pk': self.test_workflow_transition.pk}
+ )
+
+ def test_workflow_transition_field_list_view_no_permission(self):
+ self._create_test_workflow_transition_field()
+
+ response = self._request_test_workflow_transition_field_list_view()
+ self.assertNotContains(
+ response=response,
+ text=self.test_workflow_transition_field.label,
+ status_code=404
+ )
+
+ def test_workflow_transition_field_list_view_with_access(self):
+ self._create_test_workflow_transition_field()
+
+ self.grant_access(
+ obj=self.test_workflow, permission=permission_workflow_edit
+ )
+
+ response = self._request_test_workflow_transition_field_list_view()
+ self.assertContains(
+ response=response,
+ text=self.test_workflow_transition_field.label,
+ status_code=200
+ )
+
+ def _request_workflow_transition_field_create_view(self):
+ return self.post(
+ viewname='document_states:setup_workflow_transition_field_create',
+ kwargs={'pk': self.test_workflow_transition.pk},
+ data={
+ 'field_type': TEST_WORKFLOW_TRANSITION_FIELD_TYPE,
+ 'name': TEST_WORKFLOW_TRANSITION_FIELD_NAME,
+ 'label': TEST_WORKFLOW_TRANSITION_FIELD_LABEL,
+ 'help_text': TEST_WORKFLOW_TRANSITION_FIELD_HELP_TEXT
+ }
+ )
+
+ def test_workflow_transition_field_create_view_no_permission(self):
+ workflow_transition_field_count = self.test_workflow_transition.fields.count()
+
+ response = self._request_workflow_transition_field_create_view()
+ self.assertEqual(response.status_code, 404)
+
+ self.assertEqual(
+ self.test_workflow_transition.fields.count(),
+ workflow_transition_field_count
+ )
+
+ def test_workflow_transition_field_create_view_with_access(self):
+ workflow_transition_field_count = self.test_workflow_transition.fields.count()
+
+ self.grant_access(
+ obj=self.test_workflow, permission=permission_workflow_edit
+ )
+
+ response = self._request_workflow_transition_field_create_view()
+ self.assertEqual(response.status_code, 302)
+
+ self.assertEqual(
+ self.test_workflow_transition.fields.count(),
+ workflow_transition_field_count + 1
+ )
+
+ def _request_workflow_transition_field_delete_view(self):
+ return self.post(
+ viewname='document_states:setup_workflow_transition_field_delete',
+ kwargs={'pk': self.test_workflow_transition_field.pk},
+ )
+
+ def test_workflow_transition_field_delete_view_no_permission(self):
+ self._create_test_workflow_transition_field()
+
+ workflow_transition_field_count = self.test_workflow_transition.fields.count()
+
+ response = self._request_workflow_transition_field_delete_view()
+ self.assertEqual(response.status_code, 404)
+
+ self.assertEqual(
+ self.test_workflow_transition.fields.count(),
+ workflow_transition_field_count
+ )
+
+ def test_workflow_transition_field_delete_view_with_access(self):
+ self._create_test_workflow_transition_field()
+
+ workflow_transition_field_count = self.test_workflow_transition.fields.count()
+
+ self.grant_access(
+ obj=self.test_workflow, permission=permission_workflow_edit
+ )
+
+ response = self._request_workflow_transition_field_delete_view()
+ self.assertEqual(response.status_code, 302)
+
+ self.assertEqual(
+ self.test_workflow_transition.fields.count(),
+ workflow_transition_field_count - 1
+ )
diff --git a/mayan/apps/document_states/urls.py b/mayan/apps/document_states/urls.py
index f37af45452..ebc3e6acd9 100644
--- a/mayan/apps/document_states/urls.py
+++ b/mayan/apps/document_states/urls.py
@@ -23,10 +23,15 @@ from .views import (
SetupWorkflowTransitionEditView,
SetupWorkflowTransitionTriggerEventListView, ToolLaunchAllWorkflows,
WorkflowDocumentListView, WorkflowInstanceDetailView,
- WorkflowInstanceTransitionView, WorkflowListView,
- WorkflowPreviewView, WorkflowStateDocumentListView, WorkflowStateListView,
+ WorkflowInstanceTransitionExecuteView, WorkflowInstanceTransitionSelectView,
+ WorkflowListView, WorkflowPreviewView, WorkflowStateDocumentListView,
+ WorkflowStateListView,
+)
+from .views.workflow_views import (
+ SetupDocumentTypeWorkflowsView, SetupWorkflowTransitionFieldCreateView,
+ SetupWorkflowTransitionFieldDeleteView,
+ SetupWorkflowTransitionFieldEditView, SetupWorkflowTransitionFieldListView
)
-from .views.workflow_views import SetupDocumentTypeWorkflowsView
urlpatterns_workflows = [
url(
@@ -36,6 +41,29 @@ urlpatterns_workflows = [
),
]
+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/$',
@@ -48,9 +76,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'^document/workflows/(?P\d+)/transitions/(?P\d+)/execute/$',
+ view=WorkflowInstanceTransitionExecuteView.as_view(),
+ name='workflow_instance_transition_execute'
),
url(
regex=r'^setup/all/$', view=SetupWorkflowListView.as_view(),
@@ -179,7 +212,9 @@ urlpatterns = [
name='workflow_state_document_list'
),
]
+
urlpatterns.extend(urlpatterns_workflows)
+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..bed57fdcbc 100644
--- a/mayan/apps/document_states/views/workflow_instance_views.py
+++ b/mayan/apps/document_states/views/workflow_instance_views.py
@@ -4,21 +4,26 @@ 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
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 ..literals import FIELD_TYPE_MAPPING, WIDGET_CLASS_MAPPING
from ..models import WorkflowInstance
from ..permissions import permission_workflow_view
__all__ = (
'DocumentWorkflowInstanceListView', 'WorkflowInstanceDetailView',
- 'WorkflowInstanceTransitionView'
+ 'WorkflowInstanceTransitionSelectView',
+ 'WorkflowInstanceTransitionExecuteView'
)
@@ -100,14 +105,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 +130,99 @@ 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] = {
+ 'class': FIELD_TYPE_MAPPING[field.field_type],
+ 'help_text': field.help_text,
+ 'label': field.label,
+ 'required': field.required,
+ }
+ if field.widget:
+ schema['widgets'][field.name] = {
+ 'class': WIDGET_CLASS_MAPPING[field.widget],
+ 'kwargs': field.get_widget_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
+ }
diff --git a/mayan/apps/document_states/views/workflow_views.py b/mayan/apps/document_states/views/workflow_views.py
index 4abab87386..008c83a108 100644
--- a/mayan/apps/document_states/views/workflow_views.py
+++ b/mayan/apps/document_states/views/workflow_views.py
@@ -30,15 +30,17 @@ from ..forms import (
)
from ..icons import (
icon_workflow_list, icon_workflow_state, icon_workflow_state_action,
- icon_workflow_transition
+ icon_workflow_transition, icon_workflow_transition_field
)
from ..links import (
link_setup_workflow_create, link_setup_workflow_state_create,
link_setup_workflow_state_action_selection,
- link_setup_workflow_transition_create
+ link_setup_workflow_transition_create,
+ link_setup_workflow_transition_field_create,
)
from ..models import (
- Workflow, WorkflowState, WorkflowStateAction, WorkflowTransition
+ Workflow, WorkflowState, WorkflowStateAction, WorkflowTransition,
+ WorkflowTransitionField
)
from ..permissions import (
permission_workflow_create, permission_workflow_delete,
@@ -731,6 +733,124 @@ class SetupWorkflowTransitionTriggerEventListView(ExternalObjectMixin, FormView)
)
+# Transition fields
+
+class SetupWorkflowTransitionFieldCreateView(ExternalObjectMixin, SingleObjectCreateView):
+ external_object_class = WorkflowTransition
+ external_object_permission = permission_workflow_edit
+ fields = (
+ 'name', 'label', 'field_type', 'help_text', 'required', 'widget',
+ 'widget_kwargs'
+ )
+ 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', 'field_type', 'help_text', 'required', 'widget',
+ 'widget_kwargs'
+ )
+ 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_field,
+ 'no_results_main_link': link_setup_workflow_transition_field_create.resolve(
+ context=RequestContext(
+ request=self.request, dict_={
+ 'object': self.external_object
+ }
+ )
+ ),
+ 'no_results_text': _(
+ 'Workflow transition fields allow adding data to the '
+ 'workflow\'s context. This additional context data can then '
+ 'be used by other elements of the workflow system like the '
+ 'workflow state actions.'
+ ),
+ 'no_results_title': _(
+ 'There are no fields for this workflow transition'
+ ),
+ '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?'),
diff --git a/mayan/apps/document_states/widgets.py b/mayan/apps/document_states/widgets.py
index 92e761b7b9..a1ccdb57d7 100644
--- a/mayan/apps/document_states/widgets.py
+++ b/mayan/apps/document_states/widgets.py
@@ -1,17 +1,6 @@
from __future__ import unicode_literals
from django import forms
-from django.utils.html import format_html_join
-
-
-def widget_transition_events(transition):
- return format_html_join(
- sep='\n', format_string='{}
', args_generator=(
- (
- transition_trigger.event_type.label,
- ) for transition_trigger in transition.trigger_events.all()
- )
- )
class WorkflowImageWidget(forms.widgets.Widget):