workflows : add an identifier to automatic and on submit jumps (#74723) #161
|
@ -2373,8 +2373,8 @@ def test_backoffice_wscall_attachment(http_requests, pub):
|
|||
assert resp.text == '<?xml version="1.0"><foo/>'
|
||||
|
||||
formdata = formdef.data_class().get(number31.id)
|
||||
assert formdata.evolution[-1].parts[0].orig_filename == 'xxx.xml'
|
||||
assert formdata.evolution[-1].parts[0].content_type == 'text/xml'
|
||||
assert formdata.evolution[-1].parts[1].orig_filename == 'xxx.xml'
|
||||
assert formdata.evolution[-1].parts[1].content_type == 'text/xml'
|
||||
assert formdata.get_substitution_variables()['attachments'].xxx.filename == 'xxx.xml'
|
||||
resp = app.get(formdata.get_substitution_variables()['attachments'].xxx.url)
|
||||
resp = resp.follow()
|
||||
|
@ -6104,3 +6104,90 @@ def test_status_visibility(pub):
|
|||
wf.store()
|
||||
resp = app.get(formdata.get_backoffice_url())
|
||||
assert resp.pyquery('.status').text() == 'st1 st2 st3'
|
||||
|
||||
|
||||
def test_workflow_track_jumps(pub):
|
||||
create_user(pub)
|
||||
create_environment(pub)
|
||||
|
||||
wf = Workflow(name='blah')
|
||||
st1 = wf.add_status('One')
|
||||
st1.id = 'one'
|
||||
st2 = wf.add_status('Two')
|
||||
st2.id = 'two'
|
||||
|
||||
st3 = wf.add_status('Three')
|
||||
st3.id = 'three'
|
||||
st4 = wf.add_status('Four')
|
||||
st4.id = 'four'
|
||||
st5 = wf.add_status('Five')
|
||||
st5.id = 'five'
|
||||
|
||||
commentable = st1.add_action('commentable', id='_commentable')
|
||||
commentable.by = ['_submitter', '_receiver']
|
||||
commentable.button_label = 'CLICK ME!'
|
||||
|
||||
jump = st1.add_action('jumponsubmit', id='_jump')
|
||||
jump.status = st2.id
|
||||
jump.identifier = 'to_two'
|
||||
|
||||
to_three = st2.add_action('choice', id='_tothree')
|
||||
to_three.label = 'ToThree'
|
||||
to_three.by = ['_receiver']
|
||||
to_three.status = 'three'
|
||||
to_three.identifier = 'to_three'
|
||||
|
||||
to_four = st3.add_action('choice', id='_tofour')
|
||||
to_four.label = 'ToFour'
|
||||
to_four.by = ['_receiver']
|
||||
to_four.status = 'four'
|
||||
to_four.identifier = 'to_four'
|
||||
|
||||
to_five = st4.add_action('jump', id='_jump')
|
||||
to_five.status = 'five'
|
||||
to_five.identifier = 'to_five'
|
||||
|
||||
wf.store()
|
||||
|
||||
formdef = FormDef.get_by_urlname('form-title')
|
||||
formdef.data_class().wipe()
|
||||
formdef.workflow = wf
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/form-title/%s/' % formdata.id)
|
||||
resp.form['comment'] = 'HELLO WORLD'
|
||||
resp = resp.form.submit('button_commentable')
|
||||
resp = resp.follow()
|
||||
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.status == 'wf-two'
|
||||
substitution_variables = formdata.get_substitution_variables()
|
||||
assert substitution_variables['form_jumps'] == ['to_two']
|
||||
assert substitution_variables['form_latest_jump'] == 'to_two'
|
||||
|
||||
resp = resp.form.submit('button_tothree')
|
||||
resp = resp.follow()
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.status == 'wf-three'
|
||||
substitution_variables = formdata.get_substitution_variables()
|
||||
assert substitution_variables['form_jumps'] == ['to_two', 'to_three']
|
||||
assert substitution_variables['form_latest_jump'] == 'to_three'
|
||||
|
||||
resp = resp.form.submit('button_tofour')
|
||||
resp = resp.follow()
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.status == 'wf-five'
|
||||
substitution_variables = formdata.get_substitution_variables()
|
||||
assert substitution_variables['form_jumps'] == [
|
||||
'to_two',
|
||||
'to_three',
|
||||
'to_four',
|
||||
'to_five',
|
||||
]
|
||||
assert substitution_variables['form_latest_jump'] == 'to_five'
|
||||
|
|
|
@ -5438,7 +5438,7 @@ def test_rich_commentable_action(pub):
|
|||
assert '<p>hello <i>world</i></p>' in resp.text
|
||||
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.evolution[-1].parts[-1].comment == '<p>hello <i>world</i></p>'
|
||||
assert formdata.evolution[-1].parts[-2].comment == '<p>hello <i>world</i></p>'
|
||||
|
||||
# check link
|
||||
resp.form['comment'] = '<p>hello <a href="http://localhost/">link</a>.</p>'
|
||||
|
@ -5446,7 +5446,7 @@ def test_rich_commentable_action(pub):
|
|||
assert '<p>hello <a href="http://localhost/" rel="nofollow">link</a>.</p>' in resp.text
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert (
|
||||
formdata.evolution[-1].parts[-1].comment
|
||||
formdata.evolution[-1].parts[-2].comment
|
||||
== '<p>hello <a href="http://localhost/" rel="nofollow">link</a>.</p>'
|
||||
)
|
||||
|
||||
|
@ -5455,7 +5455,7 @@ def test_rich_commentable_action(pub):
|
|||
resp = resp.form.submit('button_x1').follow()
|
||||
assert '<p>hello evil</p>' in resp.text
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.evolution[-1].parts[-1].comment == '<p>hello evil</p>'
|
||||
assert formdata.evolution[-1].parts[-2].comment == '<p>hello evil</p>'
|
||||
|
||||
resp.form['comment'] = '<p></p>' # left empty
|
||||
resp = resp.form.submit('button_x1')
|
||||
|
@ -5486,7 +5486,7 @@ def test_rich_commentable_action(pub):
|
|||
resp.form['comment'] = '<p>hello<br>world</p>'
|
||||
resp = resp.form.submit('button_x1').follow()
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.evolution[-1].parts[-1].comment == '<p>hello<br>world</p>'
|
||||
assert formdata.evolution[-1].parts[-2].comment == '<p>hello<br>world</p>'
|
||||
pub.substitutions.feed(formdata)
|
||||
context = pub.substitutions.get_context_variables(mode='lazy')
|
||||
tmpl = Template('{{form_comment}}')
|
||||
|
|
|
@ -168,12 +168,14 @@ def test_trigger_jumps(pub):
|
|||
formdata.id = 1
|
||||
formdata.data = {'0': 'Alice', '1': 'alice@example.net'}
|
||||
formdata.status = 'wf-%s' % st1.id
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
id1 = formdata.id
|
||||
formdata = formdef.data_class()()
|
||||
formdata.id = 2
|
||||
formdata.data = {'0': 'Bob', '1': 'bob@example.net'}
|
||||
formdata.status = 'wf-%s' % st1.id
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
id2 = formdata.id
|
||||
select_and_jump_formdata(formdef, trigger, rows)
|
||||
|
|
|
@ -790,7 +790,7 @@ class FormData(StorableObject):
|
|||
return None
|
||||
|
||||
def jump_status(self, status_id, user_id=None):
|
||||
from wcs.workflows import ContentSnapshotPart
|
||||
from wcs.workflows import ContentSnapshotPart, JumpEvolutionPart
|
||||
|
||||
if status_id == '_previous':
|
||||
previous_status = self.pop_previous_marked_status()
|
||||
|
@ -811,7 +811,11 @@ class FormData(StorableObject):
|
|||
self.status == status
|
||||
and self.evolution[-1].status == status
|
||||
and not self.evolution[-1].comment
|
||||
and not [x for x in self.evolution[-1].parts or [] if not isinstance(x, ContentSnapshotPart)]
|
||||
and not [
|
||||
x
|
||||
for x in self.evolution[-1].parts or []
|
||||
if not isinstance(x, (ContentSnapshotPart, JumpEvolutionPart))
|
||||
]
|
||||
):
|
||||
# if status do not change and last evolution is empty,
|
||||
# just update last jump time on last evolution, do not add one
|
||||
|
|
|
@ -1011,6 +1011,29 @@ class LazyFormData(LazyFormDef):
|
|||
data = self._formdata.workflow_data or {}
|
||||
return {k: data[k] for k in data if not k.startswith('_')}
|
||||
|
||||
@property
|
||||
def jumps(self):
|
||||
from wcs.workflows import JumpEvolutionPart
|
||||
|
||||
jump_parts = []
|
||||
for part in self._formdata.iter_evolution_parts():
|
||||
if not isinstance(part, JumpEvolutionPart):
|
||||
continue
|
||||
jump_parts.append(part.identifier or 'undefined')
|
||||
return jump_parts
|
||||
|
||||
@property
|
||||
def latest_jump(self):
|
||||
from wcs.workflows import JumpEvolutionPart
|
||||
|
||||
if self._formdata.evolution:
|
||||
for evolution in reversed(self._formdata.evolution or []):
|
||||
if evolution.parts:
|
||||
for part in reversed(evolution.parts):
|
||||
if isinstance(part, JumpEvolutionPart):
|
||||
return part.identifier or 'undefined'
|
||||
return ''
|
||||
|
||||
def export_to_json(self, include_files=True):
|
||||
# this gets used to generate an email attachment :/
|
||||
return self._formdata.export_to_json(include_files=include_files)
|
||||
|
|
|
@ -21,7 +21,6 @@ from wcs.qommon.form import (
|
|||
CheckboxWidget,
|
||||
ComputedExpressionWidget,
|
||||
SingleSelectWidget,
|
||||
VarnameWidget,
|
||||
WidgetList,
|
||||
WysiwygTextWidget,
|
||||
)
|
||||
|
@ -35,7 +34,6 @@ class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
ok_in_global_action = True
|
||||
|
||||
label = None
|
||||
identifier = None
|
||||
by = []
|
||||
backoffice_info_text = None
|
||||
require_confirmation = False
|
||||
|
@ -116,6 +114,7 @@ class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
formdata.record_workflow_event('button', action_item_id=self.id)
|
||||
evo.status = 'wf-%s' % wf_status[0].id
|
||||
self.handle_markers_stack(formdata)
|
||||
self.add_jump_part(formdata, evo)
|
||||
form.clear_errors()
|
||||
return True # get out of processing loop
|
||||
|
||||
|
@ -156,14 +155,6 @@ class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
title=_('Information Text for Backoffice'),
|
||||
value=self.backoffice_info_text,
|
||||
)
|
||||
if 'identifier' in parameters:
|
||||
form.add(
|
||||
VarnameWidget,
|
||||
'%sidentifier' % prefix,
|
||||
title=_('Identifier'),
|
||||
value=self.identifier,
|
||||
advanced=True,
|
||||
)
|
||||
if 'ignore_form_errors' in parameters:
|
||||
form.add(
|
||||
CheckboxWidget,
|
||||
|
|
|
@ -42,6 +42,7 @@ JUMP_TIMEOUT_INTERVAL = max((60 // int(os.environ.get('WCS_JUMP_TIMEOUT_CHECKS',
|
|||
|
||||
def jump_and_perform(formdata, action, workflow_data=None):
|
||||
action.handle_markers_stack(formdata)
|
||||
action.add_jump_part(formdata)
|
||||
if workflow_data:
|
||||
formdata.update_workflow_data(workflow_data)
|
||||
formdata.store()
|
||||
|
@ -186,7 +187,7 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
def get_parameters(self):
|
||||
if hasattr(self, 'parent') and isinstance(self.parent, WorkflowGlobalAction):
|
||||
return ('status', 'condition', 'set_marker_on_status')
|
||||
return ('status', 'condition', 'trigger', 'by', 'timeout', 'set_marker_on_status')
|
||||
return ('status', 'condition', 'trigger', 'by', 'timeout', 'set_marker_on_status', 'identifier')
|
||||
|
||||
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
|
||||
super().add_parameters_widgets(form, parameters, prefix, formdef, **kwargs)
|
||||
|
@ -260,7 +261,9 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
wf_status = self.get_target_status(formdata)
|
||||
if wf_status:
|
||||
self.handle_markers_stack(formdata)
|
||||
self.add_jump_part(formdata)
|
||||
formdata.status = 'wf-%s' % wf_status[0].id
|
||||
formdata.store()
|
||||
|
||||
def check_condition(self, formdata, *args, trigger=None, **kwargs):
|
||||
result = super().check_condition(formdata, *args, **kwargs)
|
||||
|
|
|
@ -45,9 +45,10 @@ class JumpOnSubmitWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
if wf_status:
|
||||
evo.status = 'wf-%s' % wf_status[0].id
|
||||
self.handle_markers_stack(formdata)
|
||||
self.add_jump_part(formdata, evo)
|
||||
|
||||
def get_parameters(self):
|
||||
return ('status', 'set_marker_on_status', 'condition')
|
||||
return ('status', 'set_marker_on_status', 'condition', 'identifier')
|
||||
|
||||
|
||||
register_item_class(JumpOnSubmitWorkflowStatusItem)
|
||||
|
|
|
@ -55,6 +55,7 @@ from .qommon.form import (
|
|||
SingleSelectWidget,
|
||||
SingleSelectWidgetWithOther,
|
||||
StringWidget,
|
||||
VarnameWidget,
|
||||
WidgetList,
|
||||
WidgetListOfRoles,
|
||||
)
|
||||
|
@ -3230,11 +3231,17 @@ class WorkflowStatusItem(XmlSerialisable):
|
|||
return '<%s>' % ' '.join(parts)
|
||||
|
||||
|
||||
class JumpEvolutionPart(EvolutionPart):
|
||||
def __init__(self, identifier):
|
||||
self.identifier = identifier
|
||||
|
||||
|
||||
class WorkflowStatusJumpItem(WorkflowStatusItem):
|
||||
status = None
|
||||
endpoint = False
|
||||
set_marker_on_status = False
|
||||
category = 'status-change'
|
||||
identifier = None
|
||||
|
||||
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
|
||||
super().add_parameters_widgets(form, parameters, prefix=prefix, formdef=formdef, **kwargs)
|
||||
|
@ -3256,9 +3263,23 @@ class WorkflowStatusJumpItem(WorkflowStatusItem):
|
|||
advanced=True,
|
||||
)
|
||||
|
||||
if 'identifier' in parameters:
|
||||
form.add(
|
||||
VarnameWidget,
|
||||
'%sidentifier' % prefix,
|
||||
title=_('Identifier'),
|
||||
value=self.identifier,
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
def get_parameters(self):
|
||||
return ('status', 'set_marker_on_status', 'condition')
|
||||
|
||||
def add_jump_part(self, formdata, evo=None):
|
||||
if evo is None:
|
||||
evo = formdata.evolution[-1]
|
||||
evo.add_part(JumpEvolutionPart(self.identifier))
|
||||
|
||||
|
||||
class NoLongerAvailableAction(WorkflowStatusItem):
|
||||
pass # marker class, loadable from pickle files but removed in migrate()
|
||||
|
|
Loading…
Reference in New Issue