Initial commit to support workflow actions.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2017-08-07 05:23:02 -04:00
parent 45bfb8961e
commit 28a1edbc72
12 changed files with 560 additions and 18 deletions

View File

@@ -18,7 +18,7 @@ from mayan.celery import app
from navigation import SourceColumn from navigation import SourceColumn
from rest_api.classes import APIEndPoint from rest_api.classes import APIEndPoint
from .classes import DocumentStateHelper from .classes import DocumentStateHelper, WorkflowAction
from .handlers import ( from .handlers import (
handler_index_document, handler_trigger_transition, launch_workflow 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_document_workflow_instance_list, link_setup_workflow_document_types,
link_setup_workflow_create, link_setup_workflow_delete, link_setup_workflow_create, link_setup_workflow_delete,
link_setup_workflow_edit, link_setup_workflow_list, link_setup_workflow_edit, link_setup_workflow_list,
link_setup_workflow_states, link_setup_workflow_state_create, link_setup_workflow_states, link_setup_workflow_state_action_delete,
link_setup_workflow_state_delete, link_setup_workflow_state_edit, link_setup_workflow_state_action_edit,
link_setup_workflow_transitions, link_setup_workflow_transition_create, 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_setup_workflow_transition_delete, link_setup_workflow_transition_edit,
link_tool_launch_all_workflows, link_workflow_instance_detail, link_tool_launch_all_workflows, link_workflow_instance_detail,
link_workflow_instance_transition, link_workflow_document_list, link_workflow_instance_transition, link_workflow_document_list,
@@ -67,9 +71,12 @@ class DocumentStatesApp(MayanAppConfig):
WorkflowInstanceLogEntry = self.get_model('WorkflowInstanceLogEntry') WorkflowInstanceLogEntry = self.get_model('WorkflowInstanceLogEntry')
WorkflowRuntimeProxy = self.get_model('WorkflowRuntimeProxy') WorkflowRuntimeProxy = self.get_model('WorkflowRuntimeProxy')
WorkflowState = self.get_model('WorkflowState') WorkflowState = self.get_model('WorkflowState')
WorkflowStateAction = self.get_model('WorkflowStateAction')
WorkflowStateRuntimeProxy = self.get_model('WorkflowStateRuntimeProxy') WorkflowStateRuntimeProxy = self.get_model('WorkflowStateRuntimeProxy')
WorkflowTransition = self.get_model('WorkflowTransition') WorkflowTransition = self.get_model('WorkflowTransition')
WorkflowAction.initialize()
ModelAttribute( ModelAttribute(
Document, 'workflow.< workflow internal name >.get_current_state', Document, 'workflow.< workflow internal name >.get_current_state',
label=_('Current state of a workflow'), description=_( label=_('Current state of a workflow'), description=_(
@@ -157,6 +164,22 @@ class DocumentStatesApp(MayanAppConfig):
source=WorkflowState, label=_('Completion'), attribute='completion' 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( SourceColumn(
source=WorkflowTransition, label=_('Origin state'), source=WorkflowTransition, label=_('Origin state'),
attribute='origin_state' attribute='origin_state'
@@ -203,6 +226,7 @@ class DocumentStatesApp(MayanAppConfig):
menu_object.bind_links( menu_object.bind_links(
links=( links=(
link_setup_workflow_state_edit, link_setup_workflow_state_edit,
link_setup_workflow_state_action_list,
link_setup_workflow_state_delete link_setup_workflow_state_delete
), sources=(WorkflowState,) ), sources=(WorkflowState,)
) )
@@ -229,6 +253,13 @@ class DocumentStatesApp(MayanAppConfig):
link_workflow_state_document_list, link_workflow_state_document_list,
), sources=(WorkflowStateRuntimeProxy,) ), 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( menu_secondary.bind_links(
links=(link_setup_workflow_list, link_setup_workflow_create), links=(link_setup_workflow_list, link_setup_workflow_create),
sources=( sources=(
@@ -242,6 +273,12 @@ class DocumentStatesApp(MayanAppConfig):
WorkflowRuntimeProxy, WorkflowRuntimeProxy,
) )
) )
menu_secondary.bind_links(
links=(link_setup_workflow_state_action_selection,),
sources=(
WorkflowState,
)
)
menu_setup.bind_links(links=(link_setup_workflow_list,)) menu_setup.bind_links(links=(link_setup_workflow_list,))
menu_sidebar.bind_links( menu_sidebar.bind_links(
links=( links=(

View File

@@ -1,7 +1,17 @@
from __future__ import unicode_literals 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 from common.classes import PropertyHelper
__all__ = ('WorkflowAction',)
logger = logging.getLogger(__name__)
class DocumentStateHelper(PropertyHelper): class DocumentStateHelper(PropertyHelper):
@staticmethod @staticmethod
@@ -11,3 +21,51 @@ class DocumentStateHelper(PropertyHelper):
def get_result(self, name): def get_result(self, name):
return self.instance.workflows.get(workflow__internal_name=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', {})
}

View File

@@ -1,10 +1,30 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
import json
from django import forms from django import forms
from django.forms.formsets import formset_factory from django.forms.formsets import formset_factory
from django.utils.translation import ugettext_lazy as _ 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): class WorkflowForm(forms.ModelForm):
@@ -13,6 +33,40 @@ class WorkflowForm(forms.ModelForm):
model = Workflow 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 WorkflowStateForm(forms.ModelForm):
class Meta: class Meta:
fields = ('initial', 'label', 'completion') fields = ('initial', 'label', 'completion')

View File

@@ -110,3 +110,28 @@ link_workflow_instance_transition_events = Link(
text=_('Transition events'), text=_('Transition events'),
view='document_states:setup_workflow_instance_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',
)

View 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')),
)

View File

@@ -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')]),
),
]

View File

@@ -1,5 +1,6 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
import json
import logging import logging
from django.conf import settings 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.db.models import F, Max, Q
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_text, python_2_unicode_compatible 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 django.utils.translation import ugettext_lazy as _
from acls.models import AccessControlList from acls.models import AccessControlList
@@ -16,6 +18,10 @@ from documents.models import Document, DocumentType
from events.models import EventType from events.models import EventType
from permissions import Permission from permissions import Permission
from .literals import (
WORKFLOW_ACTION_WHEN_CHOICES, WORKFLOW_ACTION_ON_ENTRY,
WORKFLOW_ACTION_ON_EXIT
)
from .managers import WorkflowManager from .managers import WorkflowManager
from .permissions import permission_workflow_transition from .permissions import permission_workflow_transition
@@ -123,6 +129,14 @@ class WorkflowState(models.Model):
self.workflow.states.all().update(initial=False) self.workflow.states.all().update(initial=False)
return super(WorkflowState, self).save(*args, **kwargs) 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): def get_documents(self):
latest_entries = WorkflowInstanceLogEntry.objects.annotate( latest_entries = WorkflowInstanceLogEntry.objects.annotate(
max_datetime=Max( max_datetime=Max(
@@ -149,6 +163,58 @@ class WorkflowState(models.Model):
).distinct() ).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 @python_2_unicode_compatible
class WorkflowTransition(models.Model): class WorkflowTransition(models.Model):
workflow = models.ForeignKey( workflow = models.ForeignKey(
@@ -156,7 +222,6 @@ class WorkflowTransition(models.Model):
verbose_name=_('Workflow') verbose_name=_('Workflow')
) )
label = models.CharField(max_length=255, verbose_name=_('Label')) label = models.CharField(max_length=255, verbose_name=_('Label'))
origin_state = models.ForeignKey( origin_state = models.ForeignKey(
WorkflowState, on_delete=models.CASCADE, WorkflowState, on_delete=models.CASCADE,
related_name='origin_transitions', verbose_name=_('Origin state') 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): if self.transition not in self.workflow_instance.get_transition_choices(_user=self.user):
raise ValidationError(_('Not a valid transition choice.')) 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 WorkflowRuntimeProxy(Workflow):
class Meta: class Meta:

View File

@@ -13,10 +13,13 @@ from .views import (
DocumentWorkflowInstanceListView, SetupWorkflowCreateView, DocumentWorkflowInstanceListView, SetupWorkflowCreateView,
SetupWorkflowDeleteView, SetupWorkflowDocumentTypesView, SetupWorkflowDeleteView, SetupWorkflowDocumentTypesView,
SetupWorkflowEditView, SetupWorkflowListView, SetupWorkflowEditView, SetupWorkflowListView,
SetupWorkflowStateCreateView, SetupWorkflowStateDeleteView, SetupWorkflowStateActionCreateView, SetupWorkflowStateActionDeleteView,
SetupWorkflowStateEditView, SetupWorkflowStateListView, SetupWorkflowStateActionEditView, SetupWorkflowStateActionListView,
SetupWorkflowTransitionListView, SetupWorkflowTransitionCreateView, SetupWorkflowStateActionSelectionView, SetupWorkflowStateCreateView,
SetupWorkflowTransitionDeleteView, SetupWorkflowTransitionEditView, SetupWorkflowStateDeleteView, SetupWorkflowStateEditView,
SetupWorkflowStateListView, SetupWorkflowTransitionListView,
SetupWorkflowTransitionCreateView, SetupWorkflowTransitionDeleteView,
SetupWorkflowTransitionEditView,
SetupWorkflowTransitionTriggerEventListView, ToolLaunchAllWorkflows, SetupWorkflowTransitionTriggerEventListView, ToolLaunchAllWorkflows,
WorkflowDocumentListView, WorkflowInstanceDetailView, WorkflowDocumentListView, WorkflowInstanceDetailView,
WorkflowInstanceTransitionView, WorkflowListView, WorkflowInstanceTransitionView, WorkflowListView,
@@ -98,6 +101,39 @@ urlpatterns = [
SetupWorkflowStateEditView.as_view(), SetupWorkflowStateEditView.as_view(),
name='setup_workflow_state_edit' 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( url(
r'^setup/workflow/transitions/(?P<pk>\d+)/delete/$', r'^setup/workflow/transitions/(?P<pk>\d+)/delete/$',
SetupWorkflowTransitionDeleteView.as_view(), SetupWorkflowTransitionDeleteView.as_view(),

View File

@@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals
from django.contrib import messages from django.contrib import messages
from django.db.utils import IntegrityError 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.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _ 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 acls.models import AccessControlList
from common.views import ( from common.views import (
AssignRemoveView, ConfirmView, FormView, SingleObjectCreateView, AssignRemoveView, ConfirmView, FormView, SingleObjectCreateView,
SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView SingleObjectDeleteView, SingleObjectDynamicFormCreateView,
SingleObjectDynamicFormEditView, SingleObjectEditView,
SingleObjectListView
) )
from documents.models import Document from documents.models import Document
from documents.views import DocumentListView from documents.views import DocumentListView
from events.classes import Event from events.classes import Event
from events.models import EventType from events.models import EventType
from .classes import WorkflowAction
from .forms import ( from .forms import (
WorkflowForm, WorkflowInstanceTransitionForm, WorkflowStateForm, WorkflowActionSelectionForm, WorkflowForm, WorkflowInstanceTransitionForm,
WorkflowTransitionForm, WorkflowTransitionTriggerEventRelationshipFormSet WorkflowStateActionDynamicForm, WorkflowStateForm, WorkflowTransitionForm,
WorkflowTransitionTriggerEventRelationshipFormSet
) )
from .models import ( from .models import (
Workflow, WorkflowInstance, WorkflowState, WorkflowTransition, Workflow, WorkflowInstance, WorkflowState, WorkflowStateAction,
WorkflowRuntimeProxy, WorkflowStateRuntimeProxy, WorkflowTransition, WorkflowRuntimeProxy, WorkflowStateRuntimeProxy,
) )
from .permissions import ( from .permissions import (
permission_workflow_create, permission_workflow_delete, permission_workflow_create, permission_workflow_delete,
@@ -223,6 +227,142 @@ class SetupWorkflowStateListView(SingleObjectListView):
return get_object_or_404(Workflow, pk=self.kwargs['pk']) 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): class SetupWorkflowStateCreateView(SingleObjectCreateView):
form_class = WorkflowStateForm form_class = WorkflowStateForm
view_permission = permission_workflow_edit view_permission = permission_workflow_edit

View File

@@ -33,6 +33,9 @@ class Tag(models.Model):
verbose_name = _('Tag') verbose_name = _('Tag')
verbose_name_plural = _('Tags') verbose_name_plural = _('Tags')
def attach_to(self, document):
self.documents.add(document)
def get_document_count(self, user): def get_document_count(self, user):
queryset = AccessControlList.objects.filter_by_access( queryset = AccessControlList.objects.filter_by_access(
permission_document_view, user, queryset=self.documents permission_document_view, user, queryset=self.documents
@@ -40,6 +43,9 @@ class Tag(models.Model):
return queryset.count() return queryset.count()
def remove_from(self, document):
self.documents.remove(document)
class DocumentTag(Tag): class DocumentTag(Tag):
class Meta: class Meta:

View File

@@ -94,7 +94,7 @@ class TagAttachActionView(MultipleObjectFormActionView):
} }
) )
else: else:
tag.documents.add(instance) tag.attach_to(instance)
messages.success( messages.success(
self.request, self.request,
_( _(
@@ -299,7 +299,7 @@ class TagRemoveActionView(MultipleObjectFormActionView):
} }
) )
else: else:
tag.documents.remove(instance) tag.remove_from(instance)
messages.success( messages.success(
self.request, self.request,
_( _(

View 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
)