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 %} + +{% 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