diff --git a/mayan/apps/document_states/apps.py b/mayan/apps/document_states/apps.py
index 2c2888bade..11b6606517 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 widget_transition_events, WorkflowLogExtraDataWidget
from .links import (
link_document_workflow_instance_list, link_setup_document_type_workflows,
link_setup_workflow_document_types, link_setup_workflow_create,
@@ -54,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):
@@ -206,22 +206,31 @@ class DocumentStatesApp(MayanAppConfig):
SourceColumn(
source=WorkflowInstanceLogEntry, label=_('Date and time'),
- attribute='datetime'
+ attribute='datetime', is_sortable=True
)
SourceColumn(
- source=WorkflowInstanceLogEntry, attribute='user'
+ source=WorkflowInstanceLogEntry, attribute='user', is_sortable=True
)
SourceColumn(
source=WorkflowInstanceLogEntry,
- attribute='transition'
+ attribute='transition__origin_state', is_sortable=True
)
SourceColumn(
source=WorkflowInstanceLogEntry,
- attribute='comment'
+ attribute='transition', is_sortable=True
)
SourceColumn(
source=WorkflowInstanceLogEntry,
- attribute='extra_data'
+ 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(
@@ -281,8 +290,16 @@ class DocumentStatesApp(MayanAppConfig):
source=WorkflowTransitionField
)
SourceColumn(
- attribute='required', is_sortable=True, source=WorkflowTransitionField,
- widget=TwoStateWidget
+ 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(
diff --git a/mayan/apps/document_states/html_widgets.py b/mayan/apps/document_states/html_widgets.py
new file mode 100644
index 0000000000..ac518160a5
--- /dev/null
+++ b/mayan/apps/document_states/html_widgets.py
@@ -0,0 +1,39 @@
+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()
+ )
+ )
+
+
+def widget_workflow_diagram(workflow):
+ return mark_safe(
+ '
'.format(
+ reverse(
+ viewname='document_states:workflow_image', kwargs={
+ 'pk': workflow.pk
+ }
+ )
+ )
+ )
+
+
+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 5c54da1aee..f30a9fdbcb 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
diff --git a/mayan/apps/document_states/literals.py b/mayan/apps/document_states/literals.py
index e18064ae0e..84242f9ab0 100644
--- a/mayan/apps/document_states/literals.py
+++ b/mayan/apps/document_states/literals.py
@@ -2,14 +2,6 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
-WORKFLOW_ACTION_ON_ENTRY = 1
-WORKFLOW_ACTION_ON_EXIT = 2
-
-WORKFLOW_ACTION_WHEN_CHOICES = (
- (WORKFLOW_ACTION_ON_ENTRY, _('On entry')),
- (WORKFLOW_ACTION_ON_EXIT, _('On exit')),
-)
-
FIELD_TYPE_CHOICE_CHAR = 1
FIELD_TYPE_CHOICE_INTEGER = 2
FIELD_TYPE_CHOICES = (
@@ -21,3 +13,20 @@ 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
+
+WORKFLOW_ACTION_WHEN_CHOICES = (
+ (WORKFLOW_ACTION_ON_ENTRY, _('On entry')),
+ (WORKFLOW_ACTION_ON_EXIT, _('On exit')),
+)
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 60e83adc06..87628d470a 100644
--- a/mayan/apps/document_states/models.py
+++ b/mayan/apps/document_states/models.py
@@ -4,6 +4,11 @@ import json
import logging
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.exceptions import PermissionDenied, ValidationError
@@ -15,7 +20,7 @@ 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
@@ -23,7 +28,7 @@ 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 (
- FIELD_TYPE_CHOICES, WORKFLOW_ACTION_WHEN_CHOICES,
+ FIELD_TYPE_CHOICES, WIDGET_CLASS_CHOICES, WORKFLOW_ACTION_WHEN_CHOICES,
WORKFLOW_ACTION_ON_ENTRY, WORKFLOW_ACTION_ON_EXIT
)
from .managers import WorkflowManager
@@ -393,6 +398,18 @@ class WorkflowTransitionField(models.Model):
'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')
@@ -402,6 +419,9 @@ class WorkflowTransitionField(models.Model):
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):
@@ -595,6 +615,13 @@ 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 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.
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..94ed6b96c2
--- /dev/null
+++ b/mayan/apps/document_states/templates/document_states/extra_data.html
@@ -0,0 +1,7 @@
+{% if value %}
+
+ {% for key, value in value.items %}
+ - {{ key }}: {{ value }}
+ {% endfor %}
+
+{% endif %}
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 4a7e66f837..2446cecd9b 100644
--- a/mayan/apps/document_states/tests/test_workflow_transition_views.py
+++ b/mayan/apps/document_states/tests/test_workflow_transition_views.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from mayan.apps.common.tests import GenericViewTestCase
from mayan.apps.documents.tests import GenericDocumentViewTestCase
+from ..literals import FIELD_TYPE_CHOICE_CHAR
from ..models import WorkflowTransition
from ..permissions import (
permission_workflow_edit, permission_workflow_view,
@@ -19,6 +20,7 @@ from .mixins import (
TEST_WORKFLOW_TRANSITION_FIELD_NAME = 'test_workflow_transition_field'
TEST_WORKFLOW_TRANSITION_FIELD_LABEL = 'test workflow transition field'
TEST_WORKFLOW_TRANSITION_FIELD_HELP_TEXT = 'test workflow transition field help test'
+TEST_WORKFLOW_TRANSITION_FIELD_TYPE = FIELD_TYPE_CHOICE_CHAR
class WorkflowTransitionViewTestCase(
@@ -164,7 +166,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(
@@ -249,6 +251,7 @@ class WorkflowTransitionFieldViewTestCase(
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
@@ -289,6 +292,7 @@ class WorkflowTransitionFieldViewTestCase(
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
diff --git a/mayan/apps/document_states/views/workflow_instance_views.py b/mayan/apps/document_states/views/workflow_instance_views.py
index 0ff1e8493d..bed57fdcbc 100644
--- a/mayan/apps/document_states/views/workflow_instance_views.py
+++ b/mayan/apps/document_states/views/workflow_instance_views.py
@@ -16,7 +16,7 @@ from mayan.apps.documents.models import Document
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
+from ..literals import FIELD_TYPE_MAPPING, WIDGET_CLASS_MAPPING
from ..models import WorkflowInstance
from ..permissions import permission_workflow_view
@@ -165,10 +165,16 @@ class WorkflowInstanceTransitionExecuteView(FormView):
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,
- 'class': FIELD_TYPE_MAPPING[field.field_type], 'kwargs': {
- }
+ 'required': field.required,
}
+ if field.widget:
+ schema['widgets'][field.name] = {
+ 'class': WIDGET_CLASS_MAPPING[field.widget],
+ 'kwargs': field.get_widget_kwargs()
+ }
return {'schema': schema}
diff --git a/mayan/apps/document_states/views/workflow_views.py b/mayan/apps/document_states/views/workflow_views.py
index 34d845458a..d69c0ad774 100644
--- a/mayan/apps/document_states/views/workflow_views.py
+++ b/mayan/apps/document_states/views/workflow_views.py
@@ -739,8 +739,10 @@ class SetupWorkflowTransitionTriggerEventListView(ExternalObjectMixin, FormView)
class SetupWorkflowTransitionFieldCreateView(ExternalObjectMixin, SingleObjectCreateView):
external_object_class = WorkflowTransition
external_object_permission = permission_workflow_edit
- fields = ('name', 'label', 'field_type', 'help_text', 'required')
-
+ fields = (
+ 'name', 'label', 'field_type', 'help_text', 'required', 'widget',
+ 'widget_kwargs'
+ )
def get_extra_context(self):
return {
'navigation_object_list': ('transition', 'workflow'),
@@ -789,7 +791,10 @@ class SetupWorkflowTransitionFieldDeleteView(SingleObjectDeleteView):
class SetupWorkflowTransitionFieldEditView(SingleObjectEditView):
- fields = ('name', 'label', 'field_type', 'help_text', 'required',)
+ fields = (
+ 'name', 'label', 'field_type', 'help_text', 'required', 'widget',
+ 'widget_kwargs'
+ )
model = WorkflowTransitionField
object_permission = permission_workflow_edit