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 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=(

View File

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

View File

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

View File

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

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
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:

View File

@@ -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(),

View File

@@ -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

View File

@@ -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:

View File

@@ -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,
_(

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
)