Initial commit to support workflow actions.
Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -18,7 +18,7 @@ from mayan.celery import app
|
||||
from navigation import SourceColumn
|
||||
from rest_api.classes import APIEndPoint
|
||||
|
||||
from .classes import DocumentStateHelper
|
||||
from .classes import DocumentStateHelper, WorkflowAction
|
||||
from .handlers import (
|
||||
handler_index_document, handler_trigger_transition, launch_workflow
|
||||
)
|
||||
@@ -26,9 +26,13 @@ from .links import (
|
||||
link_document_workflow_instance_list, link_setup_workflow_document_types,
|
||||
link_setup_workflow_create, link_setup_workflow_delete,
|
||||
link_setup_workflow_edit, link_setup_workflow_list,
|
||||
link_setup_workflow_states, link_setup_workflow_state_create,
|
||||
link_setup_workflow_state_delete, link_setup_workflow_state_edit,
|
||||
link_setup_workflow_transitions, link_setup_workflow_transition_create,
|
||||
link_setup_workflow_states, link_setup_workflow_state_action_delete,
|
||||
link_setup_workflow_state_action_edit,
|
||||
link_setup_workflow_state_action_list,
|
||||
link_setup_workflow_state_action_selection,
|
||||
link_setup_workflow_state_create, link_setup_workflow_state_delete,
|
||||
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_tool_launch_all_workflows, link_workflow_instance_detail,
|
||||
link_workflow_instance_transition, link_workflow_document_list,
|
||||
@@ -67,9 +71,12 @@ class DocumentStatesApp(MayanAppConfig):
|
||||
WorkflowInstanceLogEntry = self.get_model('WorkflowInstanceLogEntry')
|
||||
WorkflowRuntimeProxy = self.get_model('WorkflowRuntimeProxy')
|
||||
WorkflowState = self.get_model('WorkflowState')
|
||||
WorkflowStateAction = self.get_model('WorkflowStateAction')
|
||||
WorkflowStateRuntimeProxy = self.get_model('WorkflowStateRuntimeProxy')
|
||||
WorkflowTransition = self.get_model('WorkflowTransition')
|
||||
|
||||
WorkflowAction.initialize()
|
||||
|
||||
ModelAttribute(
|
||||
Document, 'workflow.< workflow internal name >.get_current_state',
|
||||
label=_('Current state of a workflow'), description=_(
|
||||
@@ -157,6 +164,22 @@ class DocumentStatesApp(MayanAppConfig):
|
||||
source=WorkflowState, label=_('Completion'), attribute='completion'
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
source=WorkflowStateAction, label=_('Label'), attribute='label'
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowStateAction, label=_('Enabled?'),
|
||||
func=lambda context: two_state_template(context['object'].enabled)
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowStateAction, label=_('When?'),
|
||||
attribute='get_when_display'
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowStateAction, label=_('Action type'),
|
||||
attribute='get_class_label'
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
source=WorkflowTransition, label=_('Origin state'),
|
||||
attribute='origin_state'
|
||||
@@ -203,6 +226,7 @@ class DocumentStatesApp(MayanAppConfig):
|
||||
menu_object.bind_links(
|
||||
links=(
|
||||
link_setup_workflow_state_edit,
|
||||
link_setup_workflow_state_action_list,
|
||||
link_setup_workflow_state_delete
|
||||
), sources=(WorkflowState,)
|
||||
)
|
||||
@@ -229,6 +253,13 @@ class DocumentStatesApp(MayanAppConfig):
|
||||
link_workflow_state_document_list,
|
||||
), sources=(WorkflowStateRuntimeProxy,)
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=(
|
||||
link_setup_workflow_state_action_edit,
|
||||
link_setup_workflow_state_action_delete,
|
||||
), sources=(WorkflowStateAction,)
|
||||
)
|
||||
|
||||
menu_secondary.bind_links(
|
||||
links=(link_setup_workflow_list, link_setup_workflow_create),
|
||||
sources=(
|
||||
@@ -242,6 +273,12 @@ class DocumentStatesApp(MayanAppConfig):
|
||||
WorkflowRuntimeProxy,
|
||||
)
|
||||
)
|
||||
menu_secondary.bind_links(
|
||||
links=(link_setup_workflow_state_action_selection,),
|
||||
sources=(
|
||||
WorkflowState,
|
||||
)
|
||||
)
|
||||
menu_setup.bind_links(links=(link_setup_workflow_list,))
|
||||
menu_sidebar.bind_links(
|
||||
links=(
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from importlib import import_module
|
||||
import logging
|
||||
|
||||
from django.apps import apps
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from common.classes import PropertyHelper
|
||||
|
||||
__all__ = ('WorkflowAction',)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DocumentStateHelper(PropertyHelper):
|
||||
@staticmethod
|
||||
@@ -11,3 +21,51 @@ class DocumentStateHelper(PropertyHelper):
|
||||
|
||||
def get_result(self, name):
|
||||
return self.instance.workflows.get(workflow__internal_name=name)
|
||||
|
||||
|
||||
class WorkflowActionMetaclass(type):
|
||||
_registry = {}
|
||||
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
new_class = super(WorkflowActionMetaclass, mcs).__new__(
|
||||
mcs, name, bases, attrs
|
||||
)
|
||||
|
||||
if not new_class.__module__ == __name__:
|
||||
mcs._registry[
|
||||
'{}.{}'.format(new_class.__module__, name)
|
||||
] = new_class
|
||||
|
||||
return new_class
|
||||
|
||||
|
||||
class WorkflowActionBase(object):
|
||||
fields = ()
|
||||
|
||||
|
||||
class WorkflowAction(six.with_metaclass(WorkflowActionMetaclass, WorkflowActionBase)):
|
||||
@classmethod
|
||||
def get(cls, name):
|
||||
return cls._registry[name]
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
return cls._registry
|
||||
|
||||
@staticmethod
|
||||
def initialize():
|
||||
for app in apps.get_app_configs():
|
||||
try:
|
||||
import_module('{}.workflow_actions'.format(app.name))
|
||||
except ImportError as exception:
|
||||
if force_text(exception) != 'No module named workflow_actions':
|
||||
logger.error(
|
||||
'Error importing %s workflow_actions.py file; %s',
|
||||
app.name, exception
|
||||
)
|
||||
|
||||
def get_form_schema(self, request=None):
|
||||
return {
|
||||
'fields': self.fields or (),
|
||||
'widgets': getattr(self, 'widgets', {})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,30 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from django import forms
|
||||
from django.forms.formsets import formset_factory
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .models import Workflow, WorkflowState, WorkflowTransition
|
||||
from common.forms import DynamicModelForm
|
||||
|
||||
from .classes import WorkflowAction
|
||||
from .models import (
|
||||
Workflow, WorkflowState, WorkflowStateAction, WorkflowTransition
|
||||
)
|
||||
|
||||
|
||||
class WorkflowActionSelectionForm(forms.Form):
|
||||
klass = forms.ChoiceField(choices=(), label=_('Action'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WorkflowActionSelectionForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['klass'].choices = [
|
||||
(
|
||||
key, klass.label
|
||||
) for key, klass in WorkflowAction.get_all().items()
|
||||
]
|
||||
|
||||
|
||||
class WorkflowForm(forms.ModelForm):
|
||||
@@ -13,6 +33,40 @@ class WorkflowForm(forms.ModelForm):
|
||||
model = Workflow
|
||||
|
||||
|
||||
class WorkflowStateActionDynamicForm(DynamicModelForm):
|
||||
class Meta:
|
||||
fields = ('label', 'when', 'enabled', 'action_data')
|
||||
model = WorkflowStateAction
|
||||
widgets = {'action_data': forms.widgets.HiddenInput}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
result = super(
|
||||
WorkflowStateActionDynamicForm, self
|
||||
).__init__(*args, **kwargs)
|
||||
if self.instance.action_data:
|
||||
for key, value in json.loads(self.instance.action_data).items():
|
||||
self.fields[key].initial = value
|
||||
|
||||
return result
|
||||
|
||||
def clean(self):
|
||||
data = super(WorkflowStateActionDynamicForm, self).clean()
|
||||
|
||||
# Consolidate the dynamic fields into a single JSON field called
|
||||
# 'action_data'.
|
||||
action_data = {}
|
||||
|
||||
for field in self.schema['fields']:
|
||||
action_data[field['name']] = data.pop(
|
||||
field['name'], field.get('default', None)
|
||||
)
|
||||
|
||||
# Flatten the queryset to a list of ids
|
||||
action_data['tags'] = list(action_data['tags'].values_list('id', flat=True))
|
||||
data['action_data'] = json.dumps(action_data)
|
||||
return data
|
||||
|
||||
|
||||
class WorkflowStateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ('initial', 'label', 'completion')
|
||||
|
||||
@@ -110,3 +110,28 @@ link_workflow_instance_transition_events = Link(
|
||||
text=_('Transition events'),
|
||||
view='document_states:setup_workflow_instance_transition_events'
|
||||
)
|
||||
|
||||
###
|
||||
|
||||
link_setup_workflow_state_action_list = Link(
|
||||
args='resolved_object.pk', permissions=(permission_workflow_edit,),
|
||||
text=_('Actions'),
|
||||
view='document_states:setup_workflow_state_action_list',
|
||||
)
|
||||
|
||||
link_setup_workflow_state_action_selection = Link(
|
||||
args='resolved_object.pk', permissions=(permission_workflow_edit,),
|
||||
text=_('Create action'),
|
||||
view='document_states:setup_workflow_state_action_selection',
|
||||
)
|
||||
|
||||
|
||||
link_setup_workflow_state_action_delete = Link(
|
||||
args='resolved_object.pk', permissions=(permission_workflow_edit,),
|
||||
tags='dangerous', text=_('Delete'),
|
||||
view='document_states:setup_workflow_state_action_delete',
|
||||
)
|
||||
link_setup_workflow_state_action_edit = Link(
|
||||
args='resolved_object.pk', permissions=(permission_workflow_edit,),
|
||||
text=_('Edit'), view='document_states:setup_workflow_state_action_edit',
|
||||
)
|
||||
|
||||
11
mayan/apps/document_states/literals.py
Normal file
11
mayan/apps/document_states/literals.py
Normal file
@@ -0,0 +1,11 @@
|
||||
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')),
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2017-08-07 06:12
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('document_states', '0008_auto_20170803_0752'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='WorkflowStateAction',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('label', models.CharField(max_length=255, verbose_name='Label')),
|
||||
('enabled', models.BooleanField(default=True, verbose_name='Enabled')),
|
||||
('when', models.PositiveIntegerField(choices=[(1, 'On entry'), (2, 'On exit')], default=1, help_text='At which moment of the state this action will execute', verbose_name='When')),
|
||||
('action_path', models.CharField(help_text='The dotted Python path to the workflow action class to execute.', max_length=128, verbose_name='Entry action path')),
|
||||
('action_data', models.TextField(blank=True, verbose_name='Entry action data')),
|
||||
('state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='actions', to='document_states.WorkflowState', verbose_name='Workflow state')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('label',),
|
||||
'verbose_name': 'Workflow state action',
|
||||
'verbose_name_plural': 'Workflow state actions',
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='workflowstateaction',
|
||||
unique_together=set([('state', 'label')]),
|
||||
),
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
@@ -8,6 +9,7 @@ from django.db import IntegrityError, models
|
||||
from django.db.models import F, Max, Q
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from acls.models import AccessControlList
|
||||
@@ -16,6 +18,10 @@ from documents.models import Document, DocumentType
|
||||
from events.models import EventType
|
||||
from permissions import Permission
|
||||
|
||||
from .literals import (
|
||||
WORKFLOW_ACTION_WHEN_CHOICES, WORKFLOW_ACTION_ON_ENTRY,
|
||||
WORKFLOW_ACTION_ON_EXIT
|
||||
)
|
||||
from .managers import WorkflowManager
|
||||
from .permissions import permission_workflow_transition
|
||||
|
||||
@@ -123,6 +129,14 @@ class WorkflowState(models.Model):
|
||||
self.workflow.states.all().update(initial=False)
|
||||
return super(WorkflowState, self).save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def entry_actions(self):
|
||||
return self.actions.filter(when=WORKFLOW_ACTION_ON_ENTRY)
|
||||
|
||||
@property
|
||||
def exit_actions(self):
|
||||
return self.actions.filter(when=WORKFLOW_ACTION_ON_EXIT)
|
||||
|
||||
def get_documents(self):
|
||||
latest_entries = WorkflowInstanceLogEntry.objects.annotate(
|
||||
max_datetime=Max(
|
||||
@@ -149,6 +163,58 @@ class WorkflowState(models.Model):
|
||||
).distinct()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class WorkflowStateAction(models.Model):
|
||||
state = models.ForeignKey(
|
||||
WorkflowState, on_delete=models.CASCADE,
|
||||
related_name='actions', verbose_name=_('Workflow state')
|
||||
)
|
||||
label = models.CharField(max_length=255, verbose_name=_('Label'))
|
||||
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||
when = models.PositiveIntegerField(
|
||||
choices=WORKFLOW_ACTION_WHEN_CHOICES,
|
||||
default=WORKFLOW_ACTION_ON_ENTRY, help_text=_(
|
||||
'At which moment of the state this action will execute'
|
||||
), verbose_name=_('When')
|
||||
)
|
||||
action_path = models.CharField(
|
||||
max_length=128, help_text=_(
|
||||
'The dotted Python path to the workflow action class to execute.'
|
||||
), verbose_name=_('Entry action path')
|
||||
)
|
||||
action_data = models.TextField(
|
||||
blank=True, verbose_name=_('Entry action data')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
class Meta:
|
||||
ordering = ('label',)
|
||||
unique_together = ('state', 'label')
|
||||
verbose_name = _('Workflow state action')
|
||||
verbose_name_plural = _('Workflow state actions')
|
||||
|
||||
def dumps(self, data):
|
||||
self.action_data = json.dumps(data)
|
||||
self.save()
|
||||
|
||||
def execute(self, context):
|
||||
self.get_class_instance().execute(context=context)
|
||||
|
||||
def get_class(self):
|
||||
return import_string(self.action_path)
|
||||
|
||||
def get_class_label(self):
|
||||
return self.get_class().label
|
||||
|
||||
def get_class_instance(self):
|
||||
return self.get_class()(**self.loads())
|
||||
|
||||
def loads(self):
|
||||
return json.loads(self.action_data)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class WorkflowTransition(models.Model):
|
||||
workflow = models.ForeignKey(
|
||||
@@ -156,7 +222,6 @@ class WorkflowTransition(models.Model):
|
||||
verbose_name=_('Workflow')
|
||||
)
|
||||
label = models.CharField(max_length=255, verbose_name=_('Label'))
|
||||
|
||||
origin_state = models.ForeignKey(
|
||||
WorkflowState, on_delete=models.CASCADE,
|
||||
related_name='origin_transitions', verbose_name=_('Origin state')
|
||||
@@ -341,6 +406,17 @@ 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)
|
||||
|
||||
for action in self.transition.origin_state.exit_actions.all():
|
||||
action.execute(context={'entry_log': self})
|
||||
|
||||
for action in self.transition.destination_state.entry_actions.all():
|
||||
action.execute(context={'entry_log': self})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class WorkflowRuntimeProxy(Workflow):
|
||||
class Meta:
|
||||
|
||||
@@ -13,10 +13,13 @@ from .views import (
|
||||
DocumentWorkflowInstanceListView, SetupWorkflowCreateView,
|
||||
SetupWorkflowDeleteView, SetupWorkflowDocumentTypesView,
|
||||
SetupWorkflowEditView, SetupWorkflowListView,
|
||||
SetupWorkflowStateCreateView, SetupWorkflowStateDeleteView,
|
||||
SetupWorkflowStateEditView, SetupWorkflowStateListView,
|
||||
SetupWorkflowTransitionListView, SetupWorkflowTransitionCreateView,
|
||||
SetupWorkflowTransitionDeleteView, SetupWorkflowTransitionEditView,
|
||||
SetupWorkflowStateActionCreateView, SetupWorkflowStateActionDeleteView,
|
||||
SetupWorkflowStateActionEditView, SetupWorkflowStateActionListView,
|
||||
SetupWorkflowStateActionSelectionView, SetupWorkflowStateCreateView,
|
||||
SetupWorkflowStateDeleteView, SetupWorkflowStateEditView,
|
||||
SetupWorkflowStateListView, SetupWorkflowTransitionListView,
|
||||
SetupWorkflowTransitionCreateView, SetupWorkflowTransitionDeleteView,
|
||||
SetupWorkflowTransitionEditView,
|
||||
SetupWorkflowTransitionTriggerEventListView, ToolLaunchAllWorkflows,
|
||||
WorkflowDocumentListView, WorkflowInstanceDetailView,
|
||||
WorkflowInstanceTransitionView, WorkflowListView,
|
||||
@@ -98,6 +101,39 @@ urlpatterns = [
|
||||
SetupWorkflowStateEditView.as_view(),
|
||||
name='setup_workflow_state_edit'
|
||||
),
|
||||
url(
|
||||
r'^setup/workflow/state/(?P<pk>\d+)/actions/$',
|
||||
SetupWorkflowStateActionListView.as_view(),
|
||||
name='setup_workflow_state_action_list'
|
||||
),
|
||||
url(
|
||||
r'^setup/workflow/state/(?P<pk>\d+)/actions/$',
|
||||
SetupWorkflowStateActionListView.as_view(),
|
||||
name='setup_workflow_state_action_list'
|
||||
),
|
||||
url(
|
||||
r'^setup/workflow/state/(?P<pk>\d+)/actions/selection/$',
|
||||
SetupWorkflowStateActionSelectionView.as_view(),
|
||||
name='setup_workflow_state_action_selection'
|
||||
),
|
||||
url(
|
||||
r'^setup/workflow/state/(?P<pk>\d+)/actions/(?P<class_path>[a-zA-Z0-9_.]+)/create/$',
|
||||
SetupWorkflowStateActionCreateView.as_view(),
|
||||
name='setup_workflow_state_action_create'
|
||||
),
|
||||
|
||||
url(
|
||||
r'^setup/workflow/state/actions/(?P<pk>\d+)/delete/$',
|
||||
SetupWorkflowStateActionDeleteView.as_view(),
|
||||
name='setup_workflow_state_action_delete'
|
||||
),
|
||||
url(
|
||||
r'^setup/workflow/state/actions/(?P<pk>\d+)/edit/$',
|
||||
SetupWorkflowStateActionEditView.as_view(),
|
||||
name='setup_workflow_state_action_edit'
|
||||
),
|
||||
|
||||
|
||||
url(
|
||||
r'^setup/workflow/transitions/(?P<pk>\d+)/delete/$',
|
||||
SetupWorkflowTransitionDeleteView.as_view(),
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.contrib import messages
|
||||
from django.db.utils import IntegrityError
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@@ -10,20 +10,24 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from acls.models import AccessControlList
|
||||
from common.views import (
|
||||
AssignRemoveView, ConfirmView, FormView, SingleObjectCreateView,
|
||||
SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView
|
||||
SingleObjectDeleteView, SingleObjectDynamicFormCreateView,
|
||||
SingleObjectDynamicFormEditView, SingleObjectEditView,
|
||||
SingleObjectListView
|
||||
)
|
||||
from documents.models import Document
|
||||
from documents.views import DocumentListView
|
||||
from events.classes import Event
|
||||
from events.models import EventType
|
||||
|
||||
from .classes import WorkflowAction
|
||||
from .forms import (
|
||||
WorkflowForm, WorkflowInstanceTransitionForm, WorkflowStateForm,
|
||||
WorkflowTransitionForm, WorkflowTransitionTriggerEventRelationshipFormSet
|
||||
WorkflowActionSelectionForm, WorkflowForm, WorkflowInstanceTransitionForm,
|
||||
WorkflowStateActionDynamicForm, WorkflowStateForm, WorkflowTransitionForm,
|
||||
WorkflowTransitionTriggerEventRelationshipFormSet
|
||||
)
|
||||
from .models import (
|
||||
Workflow, WorkflowInstance, WorkflowState, WorkflowTransition,
|
||||
WorkflowRuntimeProxy, WorkflowStateRuntimeProxy,
|
||||
Workflow, WorkflowInstance, WorkflowState, WorkflowStateAction,
|
||||
WorkflowTransition, WorkflowRuntimeProxy, WorkflowStateRuntimeProxy,
|
||||
)
|
||||
from .permissions import (
|
||||
permission_workflow_create, permission_workflow_delete,
|
||||
@@ -223,6 +227,142 @@ class SetupWorkflowStateListView(SingleObjectListView):
|
||||
return get_object_or_404(Workflow, pk=self.kwargs['pk'])
|
||||
|
||||
|
||||
class SetupWorkflowStateActionCreateView(SingleObjectDynamicFormCreateView):
|
||||
form_class = WorkflowStateActionDynamicForm
|
||||
object_permission = permission_workflow_edit
|
||||
|
||||
def get_class(self):
|
||||
try:
|
||||
return WorkflowAction.get(name=self.kwargs['class_path'])
|
||||
except KeyError:
|
||||
raise Http404(
|
||||
'{} class not found'.format(self.kwargs['class_path'])
|
||||
)
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'navigation_object_list': ('object', 'workflow'),
|
||||
'object': self.get_object(),
|
||||
'title': _(
|
||||
'Create a "%s" workflow action'
|
||||
) % self.get_class().label,
|
||||
'workflow': self.get_object().workflow
|
||||
}
|
||||
|
||||
def get_form_schema(self):
|
||||
return self.get_class()().get_form_schema(request=self.request)
|
||||
|
||||
def get_instance_extra_data(self):
|
||||
return {
|
||||
'action_path': self.kwargs['class_path'],
|
||||
'state': self.get_object()
|
||||
}
|
||||
|
||||
def get_object(self):
|
||||
return get_object_or_404(WorkflowState, pk=self.kwargs['pk'])
|
||||
|
||||
def get_post_action_redirect(self):
|
||||
return reverse(
|
||||
'document_states:setup_workflow_state_action_list',
|
||||
args=(self.get_object().pk,)
|
||||
)
|
||||
|
||||
|
||||
class SetupWorkflowStateActionDeleteView(SingleObjectDeleteView):
|
||||
model = WorkflowStateAction
|
||||
object_permission = permission_workflow_edit
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'navigation_object_list': (
|
||||
'object', 'workflow_state', 'workflow'
|
||||
),
|
||||
'object': self.get_object(),
|
||||
'title': _('Delete workflow state action: %s') % self.get_object(),
|
||||
'workflow': self.get_object().state.workflow,
|
||||
'workflow_state': self.get_object().state,
|
||||
}
|
||||
|
||||
def get_post_action_redirect(self):
|
||||
return reverse(
|
||||
'document_states:setup_workflow_state_action_list',
|
||||
args=(self.get_object().state.pk,)
|
||||
)
|
||||
|
||||
|
||||
class SetupWorkflowStateActionEditView(SingleObjectDynamicFormEditView):
|
||||
form_class = WorkflowStateActionDynamicForm
|
||||
model = WorkflowStateAction
|
||||
object_permission = permission_workflow_edit
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'navigation_object_list': (
|
||||
'object', 'workflow_state', 'workflow'
|
||||
),
|
||||
'object': self.get_object(),
|
||||
'title': _('Edit workflow state action: %s') % self.get_object(),
|
||||
'workflow': self.get_object().state.workflow,
|
||||
'workflow_state': self.get_object().state,
|
||||
}
|
||||
|
||||
def get_form_schema(self):
|
||||
return self.get_object().get_class_instance().get_form_schema(
|
||||
request=self.request
|
||||
)
|
||||
|
||||
|
||||
class SetupWorkflowStateActionListView(SingleObjectListView):
|
||||
object_permission = permission_workflow_edit
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'hide_object': True,
|
||||
'navigation_object_list': ('object', 'workflow'),
|
||||
'object': self.get_workflow_state(),
|
||||
'title': _(
|
||||
'Actions for workflow state: %s'
|
||||
) % self.get_workflow_state(),
|
||||
'workflow': self.get_workflow_state().workflow,
|
||||
}
|
||||
|
||||
def get_form_schema(self):
|
||||
return {'fields': self.get_class().fields}
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_workflow_state().actions.all()
|
||||
|
||||
def get_workflow_state(self):
|
||||
return get_object_or_404(WorkflowState, pk=self.kwargs['pk'])
|
||||
|
||||
|
||||
class SetupWorkflowStateActionSelectionView(FormView):
|
||||
form_class = WorkflowActionSelectionForm
|
||||
#TODO: access check via workflow edit perm
|
||||
|
||||
def form_valid(self, form):
|
||||
klass = form.cleaned_data['klass']
|
||||
return HttpResponseRedirect(
|
||||
reverse(
|
||||
'document_states:setup_workflow_state_action_create',
|
||||
args=(self.get_object().pk, klass,),
|
||||
)
|
||||
)
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'navigation_object_list': (
|
||||
'object', 'workflow'
|
||||
),
|
||||
'object': self.get_object(),
|
||||
'title': _('New workflow state action selection'),
|
||||
'workflow': self.get_object().workflow,
|
||||
}
|
||||
|
||||
def get_object(self):
|
||||
return get_object_or_404(WorkflowState, pk=self.kwargs['pk'])
|
||||
|
||||
|
||||
class SetupWorkflowStateCreateView(SingleObjectCreateView):
|
||||
form_class = WorkflowStateForm
|
||||
view_permission = permission_workflow_edit
|
||||
|
||||
@@ -33,6 +33,9 @@ class Tag(models.Model):
|
||||
verbose_name = _('Tag')
|
||||
verbose_name_plural = _('Tags')
|
||||
|
||||
def attach_to(self, document):
|
||||
self.documents.add(document)
|
||||
|
||||
def get_document_count(self, user):
|
||||
queryset = AccessControlList.objects.filter_by_access(
|
||||
permission_document_view, user, queryset=self.documents
|
||||
@@ -40,6 +43,9 @@ class Tag(models.Model):
|
||||
|
||||
return queryset.count()
|
||||
|
||||
def remove_from(self, document):
|
||||
self.documents.remove(document)
|
||||
|
||||
|
||||
class DocumentTag(Tag):
|
||||
class Meta:
|
||||
|
||||
@@ -94,7 +94,7 @@ class TagAttachActionView(MultipleObjectFormActionView):
|
||||
}
|
||||
)
|
||||
else:
|
||||
tag.documents.add(instance)
|
||||
tag.attach_to(instance)
|
||||
messages.success(
|
||||
self.request,
|
||||
_(
|
||||
@@ -299,7 +299,7 @@ class TagRemoveActionView(MultipleObjectFormActionView):
|
||||
}
|
||||
)
|
||||
else:
|
||||
tag.documents.remove(instance)
|
||||
tag.remove_from(instance)
|
||||
messages.success(
|
||||
self.request,
|
||||
_(
|
||||
|
||||
62
mayan/apps/tags/workflow_actions.py
Normal file
62
mayan/apps/tags/workflow_actions.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from acls.models import AccessControlList
|
||||
from document_states.classes import WorkflowAction
|
||||
from tags.models import Tag
|
||||
from tags.permissions import permission_tag_view
|
||||
|
||||
__all__ = ('AttachTagAction',)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AttachTagAction(WorkflowAction):
|
||||
fields = (
|
||||
{
|
||||
'name': 'tags', 'label': _('Tags'),
|
||||
'class': 'django.forms.ModelMultipleChoiceField', 'kwargs': {
|
||||
'help_text': _('Tags to attach to the document'),
|
||||
'queryset': Tag.objects.none(), 'required': False
|
||||
}
|
||||
},
|
||||
)
|
||||
label = _('Attach tag')
|
||||
widgets = {
|
||||
'tags': {
|
||||
'class': 'tags.widgets.TagFormWidget', 'kwargs': {
|
||||
'attrs': {'class': 'select2-tags'},
|
||||
'queryset': Tag.objects.none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, tags=None):
|
||||
if tags:
|
||||
self.tags = Tag.objects.filter(pk__in=tags)
|
||||
else:
|
||||
self.tags = Tag.objects.none()
|
||||
|
||||
def get_form_schema(self, request):
|
||||
user = request.user
|
||||
logger.debug('user: %s', user)
|
||||
|
||||
queryset = AccessControlList.objects.filter_by_access(
|
||||
permission_tag_view, user, queryset=Tag.objects.all()
|
||||
)
|
||||
|
||||
self.fields[0]['kwargs']['queryset'] = queryset
|
||||
self.widgets['tags']['kwargs']['queryset'] = queryset
|
||||
|
||||
return {
|
||||
'fields': self.fields,
|
||||
'widgets': self.widgets
|
||||
}
|
||||
|
||||
def execute(self, context):
|
||||
for tag in self.tags:
|
||||
tag.attach_to(
|
||||
document=context['entry_log'].workflow_instance.document
|
||||
)
|
||||
Reference in New Issue
Block a user