240 lines
8.9 KiB
Python
240 lines
8.9 KiB
Python
# w.c.s. - web application for online forms
|
|
# Copyright (C) 2005-2020 Entr'ouvert
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from quixote import get_publisher
|
|
|
|
from wcs.carddef import CardDef
|
|
from wcs.formdef import FormDef
|
|
from wcs.qommon import _
|
|
from wcs.qommon.form import ComputedExpressionWidget, Form, RadiobuttonsWidget, SingleSelectWidget
|
|
from wcs.workflows import (
|
|
Workflow,
|
|
WorkflowGlobalActionWebserviceTrigger,
|
|
WorkflowStatusItem,
|
|
perform_items,
|
|
register_item_class,
|
|
)
|
|
|
|
|
|
class ExternalWorkflowGlobalAction(WorkflowStatusItem):
|
|
|
|
description = _('External workflow')
|
|
key = 'external_workflow_global_action'
|
|
category = 'formdata-action'
|
|
automatic_targetting = _('Action on linked cards/forms')
|
|
manual_targetting = _('Specify the identifier of the card/form on which the action will be applied')
|
|
|
|
slug = None
|
|
target_mode = None
|
|
target_id = None
|
|
trigger_id = None
|
|
|
|
@classmethod
|
|
def is_available(cls, workflow=None):
|
|
return get_publisher().has_site_option('external-workflow')
|
|
|
|
def get_workflow_webservice_triggers(self, workflow):
|
|
for action in workflow.global_actions or []:
|
|
for trigger in action.triggers or []:
|
|
if isinstance(trigger, WorkflowGlobalActionWebserviceTrigger) and trigger.identifier:
|
|
yield trigger
|
|
|
|
def get_object_def(self, object_slug=None):
|
|
slug = object_slug or self.slug
|
|
try:
|
|
object_type, slug = slug.split(':')
|
|
except (AttributeError, ValueError):
|
|
return None
|
|
if object_type == 'formdef':
|
|
object_class = FormDef
|
|
elif object_type == 'carddef':
|
|
object_class = CardDef
|
|
try:
|
|
return object_class.get_by_urlname(slug)
|
|
except KeyError:
|
|
pass
|
|
|
|
def get_trigger(self, workflow):
|
|
try:
|
|
trigger_id = self.trigger_id.split(':', 1)[1]
|
|
except ValueError:
|
|
return
|
|
for trigger in self.get_workflow_webservice_triggers(workflow):
|
|
if trigger.identifier == trigger_id:
|
|
return trigger
|
|
|
|
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
|
|
super().add_parameters_widgets(form, parameters, prefix=prefix, formdef=formdef, **kwargs)
|
|
|
|
if 'slug' in parameters:
|
|
objects = [(None, '---', '')]
|
|
for wf in Workflow.select():
|
|
if any(self.get_workflow_webservice_triggers(wf)):
|
|
for objectdef in wf.formdefs(lightweight=True) + wf.carddefs(lightweight=True):
|
|
object_slug = '%s:%s' % (objectdef.__class__.__name__.lower(), objectdef.url_name)
|
|
objects.append((object_slug, objectdef.name, object_slug))
|
|
if len(objects) == 1:
|
|
form.add_global_errors([_('No workflow with external triggerable global action.')])
|
|
return
|
|
objects.sort(key=lambda x: x[1])
|
|
form.add(
|
|
SingleSelectWidget,
|
|
'%sslug' % prefix,
|
|
title=_('Form/Card'),
|
|
value=self.slug,
|
|
required=True,
|
|
options=objects,
|
|
)
|
|
|
|
if 'target_mode' in parameters:
|
|
target_modes = [
|
|
('all', self.automatic_targetting, 'all'),
|
|
('manual', self.manual_targetting, 'manual'),
|
|
]
|
|
form.add(
|
|
RadiobuttonsWidget,
|
|
'%starget_mode' % prefix,
|
|
title=_('Targeting'),
|
|
value=self.target_mode or 'all',
|
|
required=True,
|
|
options=target_modes,
|
|
attrs={'data-dynamic-display-parent': 'true'},
|
|
)
|
|
|
|
if 'target_id' in parameters:
|
|
form.add(
|
|
ComputedExpressionWidget,
|
|
'%starget_id' % prefix,
|
|
value=self.target_id,
|
|
required=False,
|
|
attrs={
|
|
'data-dynamic-display-child-of': 'target_mode',
|
|
'data-dynamic-display-value': 'manual',
|
|
},
|
|
)
|
|
|
|
if 'trigger_id' in parameters and form.get('%sslug' % prefix):
|
|
object_def = self.get_object_def(form.get('%sslug' % prefix))
|
|
if not object_def:
|
|
return
|
|
triggers_names = [(None, '---', '')]
|
|
for trigger in self.get_workflow_webservice_triggers(object_def.workflow):
|
|
trigger_id = 'action:%s' % trigger.identifier
|
|
triggers_names.append((trigger_id, trigger.parent.name, trigger_id))
|
|
form.add(
|
|
SingleSelectWidget,
|
|
'%strigger_id' % prefix,
|
|
title=_('Action'),
|
|
value=self.trigger_id,
|
|
required=True,
|
|
options=triggers_names,
|
|
)
|
|
|
|
if kwargs.get('orig') == 'variable_widget':
|
|
return
|
|
|
|
errors = [w.name for w in form.get_all_widgets() if w.has_error()]
|
|
if set(errors) == set(['%strigger_id' % prefix]):
|
|
form.ERROR_NOTICE = _('This action is configured in two steps. See below for details.')
|
|
else:
|
|
form.ERROR_NOTICE = Form.ERROR_NOTICE
|
|
|
|
def get_line_details(self):
|
|
if self.slug and self.trigger_id:
|
|
objectdef = self.get_object_def()
|
|
if objectdef:
|
|
trigger = self.get_trigger(objectdef.workflow)
|
|
if trigger:
|
|
return _('action "%(trigger_name)s" on %(object_name)s') % {
|
|
'trigger_name': trigger.parent.name,
|
|
'object_name': objectdef.name,
|
|
}
|
|
return _('not completed')
|
|
|
|
def get_manual_target(self, formdata):
|
|
if self.target_mode != 'manual':
|
|
return
|
|
|
|
objectdef = self.get_object_def()
|
|
target_id = self.compute(self.target_id, formdata=formdata, status_item=self)
|
|
if not target_id:
|
|
return
|
|
|
|
try:
|
|
return objectdef.data_class().get(target_id)
|
|
except KeyError as e:
|
|
# use custom error message depending on target type
|
|
get_publisher().record_error(
|
|
_('Could not find targeted "%(object_name)s" object by id %(object_id)s')
|
|
% {'object_name': objectdef.name, 'object_id': target_id},
|
|
formdata=formdata,
|
|
status_item=self,
|
|
exception=e,
|
|
)
|
|
|
|
def iter_target_datas(self, formdata, objectdef):
|
|
if self.target_mode == 'manual':
|
|
# return only target
|
|
target = self.get_manual_target(formdata)
|
|
if target:
|
|
yield target
|
|
return
|
|
|
|
yield from formdata.iter_target_datas(objectdef=objectdef, object_type=self.slug, status_item=self)
|
|
|
|
def get_parameters(self):
|
|
return ('slug', 'trigger_id', 'target_mode', 'target_id', 'condition')
|
|
|
|
def perform(self, formdata):
|
|
objectdef = self.get_object_def()
|
|
if not objectdef:
|
|
return
|
|
|
|
trigger = self.get_trigger(objectdef.workflow)
|
|
if not trigger:
|
|
get_publisher().record_error(
|
|
_('No trigger with id "%s" found in workflow') % self.trigger_id,
|
|
formdata=formdata,
|
|
status_item=self,
|
|
)
|
|
return
|
|
|
|
class CallerSource:
|
|
def __init__(self, formdata):
|
|
self.formdata = formdata
|
|
|
|
def get_substitution_variables(self):
|
|
return {'caller_form': self.formdata.get_substitution_variables(minimal=True)['form']}
|
|
|
|
caller_source = CallerSource(formdata)
|
|
|
|
formdata.store()
|
|
for target_data in self.iter_target_datas(formdata, objectdef):
|
|
with get_publisher().substitutions.temporary_feed(target_data):
|
|
get_publisher().substitutions.reset()
|
|
get_publisher().substitutions.feed(get_publisher())
|
|
get_publisher().substitutions.feed(target_data.formdef)
|
|
get_publisher().substitutions.feed(target_data)
|
|
get_publisher().substitutions.feed(caller_source)
|
|
perform_items(trigger.parent.items, target_data)
|
|
|
|
# update local object as it may have been modified by target_data
|
|
# workflow executions.
|
|
formdata.refresh_from_storage()
|
|
|
|
|
|
register_item_class(ExternalWorkflowGlobalAction)
|