0001-workflows-use-check_condition-method-to-evaluate-jum.patch
tests/form_pages/test_all.py | ||
---|---|---|
4800 | 4800 |
assert formdata.evolution[1].comment == 'new-evolution-2' |
4801 | 4801 |
assert formdata.evolution[2].comment is None |
4802 | 4802 | |
4803 |
# mark user as owner so it can check the UI |
|
4804 |
formdata.user_id = user.id |
|
4805 |
formdata.store() |
|
4803 | 4806 |
resp = app.get(formdata.get_url()) |
4804 | 4807 |
assert resp.text.count('Status1') == 3 # once in summary and two in journal |
4805 | 4808 |
assert resp.text.count('CSS-STATUS1') == 2 |
... | ... | |
5181 | 5184 |
formdata.evolution[-1].add_part(JournalWsCallErrorPart('bla', 'bla', {})) |
5182 | 5185 |
formdata.store() |
5183 | 5186 | |
5187 |
# mark user as owner so it can check the UI |
|
5188 |
formdata.user_id = user.id |
|
5189 |
formdata.store() |
|
5184 | 5190 |
resp = app.get(formdata.get_url()) |
5185 | 5191 |
assert resp.text.count('<li class="msg') == 1 |
5186 | 5192 | |
... | ... | |
8483 | 8489 | |
8484 | 8490 |
formdata.refresh_from_storage() |
8485 | 8491 |
assert not formdata.workflow_data |
8492 | ||
8493 | ||
8494 |
def test_jumps_with_by_and_no_trigger(pub): |
|
8495 |
FormDef.wipe() |
|
8496 |
Workflow.wipe() |
|
8497 |
pub.role_class.wipe() |
|
8498 | ||
8499 |
role = pub.role_class(name='xxx') |
|
8500 |
role.store() |
|
8501 | ||
8502 |
workflow = Workflow(name='test') |
|
8503 |
st1 = workflow.add_status('Status1', 'st1') |
|
8504 | ||
8505 |
jump = JumpWorkflowStatusItem() |
|
8506 |
jump.status = 'st2' |
|
8507 |
st1.items.append(jump) |
|
8508 |
jump.by = [role.id] |
|
8509 |
jump.parent = st1 |
|
8510 | ||
8511 |
jump = JumpWorkflowStatusItem() |
|
8512 |
jump.status = 'st3' |
|
8513 |
st1.items.append(jump) |
|
8514 |
jump.by = [] |
|
8515 |
jump.parent = st1 |
|
8516 | ||
8517 |
workflow.add_status('Status2', 'st2') |
|
8518 |
workflow.add_status('Status3', 'st3') |
|
8519 |
workflow.store() |
|
8520 | ||
8521 |
formdef = create_formdef() |
|
8522 |
formdef.fields = [] |
|
8523 |
formdef.workflow = workflow |
|
8524 |
formdef.store() |
|
8525 |
formdef.data_class().wipe() |
|
8526 | ||
8527 |
resp = get_app(pub).get('/test/') |
|
8528 |
resp = resp.form.submit('submit') |
|
8529 |
resp = resp.form.submit('submit') |
|
8530 | ||
8531 |
# it jumps to st2, as jump.by is only related to triggers |
|
8532 |
assert formdef.data_class().count() == 1 |
|
8533 |
assert formdef.data_class().select()[0].status == 'wf-st2' |
tests/workflow/test_all.py | ||
---|---|---|
252 | 252 |
formdef.store() |
253 | 253 |
formdata = formdef.data_class()() |
254 | 254 |
item = JumpWorkflowStatusItem() |
255 |
assert item.must_jump(formdata) is True
|
|
255 |
assert item.check_condition(formdata) is True
|
|
256 | 256 | |
257 | 257 | |
258 | 258 |
def test_jump_datetime_condition(pub): |
... | ... | |
267 | 267 |
'type': 'python', |
268 | 268 |
'value': 'datetime.datetime.now() > datetime.datetime(%s, %s, %s)' % yesterday.timetuple()[:3], |
269 | 269 |
} |
270 |
assert item.must_jump(formdata) is True
|
|
270 |
assert item.check_condition(formdata) is True
|
|
271 | 271 | |
272 | 272 |
tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) |
273 | 273 |
item.condition = { |
274 | 274 |
'type': 'python', |
275 | 275 |
'value': 'datetime.datetime.now() > datetime.datetime(%s, %s, %s)' % tomorrow.timetuple()[:3], |
276 | 276 |
} |
277 |
assert item.must_jump(formdata) is False
|
|
277 |
assert item.check_condition(formdata) is False
|
|
278 | 278 | |
279 | 279 | |
280 | 280 |
def test_jump_date_conditions(pub): |
... | ... | |
297 | 297 |
'type': 'python', |
298 | 298 |
'value': 'utils.make_date(form_var_date) == utils.make_date("2015-01-04")', |
299 | 299 |
} |
300 |
assert item.must_jump(formdata) is True
|
|
300 |
assert item.check_condition(formdata) is True
|
|
301 | 301 | |
302 | 302 |
item = JumpWorkflowStatusItem() |
303 | 303 |
item.condition = {'type': 'python', 'value': 'utils.time_delta(form_var_date, "2015-01-04").days == 0'} |
304 |
assert item.must_jump(formdata) is True
|
|
304 |
assert item.check_condition(formdata) is True
|
|
305 | 305 | |
306 | 306 |
item = JumpWorkflowStatusItem() |
307 | 307 |
item.condition = {'type': 'python', 'value': 'utils.time_delta(utils.today(), "2015-01-04").days > 0'} |
308 |
assert item.must_jump(formdata) is True
|
|
308 |
assert item.check_condition(formdata) is True
|
|
309 | 309 | |
310 | 310 |
item = JumpWorkflowStatusItem() |
311 | 311 |
item.condition = { |
312 | 312 |
'type': 'python', |
313 | 313 |
'value': 'utils.time_delta(datetime.datetime.now(), "2015-01-04").days > 0', |
314 | 314 |
} |
315 |
assert item.must_jump(formdata) is True
|
|
315 |
assert item.check_condition(formdata) is True
|
|
316 | 316 | |
317 | 317 |
item = JumpWorkflowStatusItem() |
318 | 318 |
item.condition = { |
319 | 319 |
'type': 'python', |
320 | 320 |
'value': 'utils.time_delta(utils.time.localtime(), "2015-01-04").days > 0', |
321 | 321 |
} |
322 |
assert item.must_jump(formdata) is True
|
|
322 |
assert item.check_condition(formdata) is True
|
|
323 | 323 | |
324 | 324 | |
325 | 325 |
def test_jump_count_condition(pub): |
... | ... | |
332 | 332 |
formdata = formdef.data_class()() |
333 | 333 |
item = JumpWorkflowStatusItem() |
334 | 334 |
item.condition = {'type': 'python', 'value': 'form_objects.count < 2'} |
335 |
assert item.must_jump(formdata) is True
|
|
335 |
assert item.check_condition(formdata) is True
|
|
336 | 336 | |
337 | 337 |
for _ in range(10): |
338 | 338 |
formdata = formdef.data_class()() |
339 | 339 |
formdata.store() |
340 | 340 | |
341 | 341 |
item.condition = {'type': 'python', 'value': 'form_objects.count < 2'} |
342 |
assert item.must_jump(formdata) is False
|
|
342 |
assert item.check_condition(formdata) is False
|
|
343 | 343 | |
344 | 344 | |
345 | 345 |
def test_jump_bad_python_condition(two_pubs): |
... | ... | |
357 | 357 |
item = JumpWorkflowStatusItem() |
358 | 358 | |
359 | 359 |
item.condition = {'type': 'python', 'value': 'form_var_foobar == 0'} |
360 |
assert item.must_jump(formdata) is False
|
|
360 |
assert item.check_condition(formdata) is False
|
|
361 | 361 |
assert two_pubs.loggederror_class.count() == 1 |
362 | 362 |
logged_error = two_pubs.loggederror_class.select()[0] |
363 | 363 |
assert logged_error.summary == 'Failed to evaluate condition' |
... | ... | |
368 | 368 | |
369 | 369 |
two_pubs.loggederror_class.wipe() |
370 | 370 |
item.condition = {'type': 'python', 'value': '~ invalid ~'} |
371 |
assert item.must_jump(formdata) is False
|
|
371 |
assert item.check_condition(formdata) is False
|
|
372 | 372 |
assert two_pubs.loggederror_class.count() == 1 |
373 | 373 |
logged_error = two_pubs.loggederror_class.select()[0] |
374 | 374 |
assert logged_error.summary == 'Failed to evaluate condition' |
... | ... | |
392 | 392 |
item = JumpWorkflowStatusItem() |
393 | 393 | |
394 | 394 |
item.condition = {'type': 'django', 'value': '1 < 2'} |
395 |
assert item.must_jump(formdata) is True
|
|
395 |
assert item.check_condition(formdata) is True
|
|
396 | 396 | |
397 | 397 |
item.condition = {'type': 'django', 'value': 'form_var_foo == "hello"'} |
398 |
assert item.must_jump(formdata) is True
|
|
398 |
assert item.check_condition(formdata) is True
|
|
399 | 399 | |
400 | 400 |
item.condition = {'type': 'django', 'value': 'form_var_foo|first|upper == "H"'} |
401 |
assert item.must_jump(formdata) is True
|
|
401 |
assert item.check_condition(formdata) is True
|
|
402 | 402 | |
403 | 403 |
item.condition = {'type': 'django', 'value': 'form_var_foo|first|upper == "X"'} |
404 |
assert item.must_jump(formdata) is False
|
|
404 |
assert item.check_condition(formdata) is False
|
|
405 | 405 | |
406 | 406 |
if two_pubs.is_using_postgresql(): |
407 | 407 |
assert two_pubs.loggederror_class.count() == 0 |
408 | 408 | |
409 | 409 |
item.condition = {'type': 'django', 'value': '~ invalid ~'} |
410 |
assert item.must_jump(formdata) is False
|
|
410 |
assert item.check_condition(formdata) is False
|
|
411 | 411 |
if two_pubs.is_using_postgresql(): |
412 | 412 |
assert two_pubs.loggederror_class.count() == 1 |
413 | 413 |
logged_error = two_pubs.loggederror_class.select()[0] |
... | ... | |
2390 | 2390 |
formdata.just_created() |
2391 | 2391 |
formdata.store() |
2392 | 2392 |
formdata_id = formdata.id |
2393 |
with mock.patch('wcs.wf.jump.JumpWorkflowStatusItem.must_jump') as must_jump:
|
|
2393 |
with mock.patch('wcs.wf.jump.JumpWorkflowStatusItem.check_condition') as must_jump:
|
|
2394 | 2394 |
must_jump.return_value = False |
2395 | 2395 |
_apply_timeouts(two_pubs) |
2396 | 2396 |
assert must_jump.call_count == 0 # not enough time has passed |
... | ... | |
6506 | 6506 |
assert two_pubs.loggederror_class.count() == 1 |
6507 | 6507 |
logged_error = two_pubs.loggederror_class.select()[0] |
6508 | 6508 |
assert logged_error.summary == 'Mismatch in target object: expected "Other data", got "Data"' |
6509 | ||
6510 | ||
6511 |
def test_conditional_jump_vs_tracing(pub): |
|
6512 |
workflow = Workflow(name='wf') |
|
6513 |
st1 = workflow.add_status('Status1', 'st1') |
|
6514 |
workflow.add_status('Status2', 'st2') |
|
6515 |
comment = st1.append_item('register-comment') |
|
6516 |
comment.comment = 'hello world' |
|
6517 |
jump1 = st1.append_item('jump') |
|
6518 |
jump1.parent = st1 |
|
6519 |
jump1.condition = {'type': 'django', 'value': 'False'} |
|
6520 |
jump1.status = 'wf-st2' |
|
6521 |
jump2 = st1.append_item('jump') |
|
6522 |
jump2.parent = st1 |
|
6523 |
jump2.status = 'wf-st2' |
|
6524 |
workflow.store() |
|
6525 | ||
6526 |
formdef = FormDef() |
|
6527 |
formdef.name = 'baz' |
|
6528 |
formdef.workflow = workflow |
|
6529 |
formdef.store() |
|
6530 | ||
6531 |
formdata = formdef.data_class()() |
|
6532 |
formdata.just_created() |
|
6533 |
formdata.store() |
|
6534 |
perform_items(st1.items, formdata) |
|
6535 |
formdata.refresh_from_storage() |
|
6536 |
assert formdata.evolution[0].parts[-1].actions[0][1:] == ('register-comment', str(comment.id)) |
|
6537 |
assert formdata.evolution[0].parts[-1].actions[1][1:] == ('jump', str(jump2.id)) |
wcs/formdata.py | ||
---|---|---|
1103 | 1103 |
for item in wf_status.items or []: |
1104 | 1104 |
if not hasattr(item, 'by') or not item.by: |
1105 | 1105 |
continue |
1106 |
if item.key == 'jump': |
|
1107 |
# automatic jump has a 'by' attribute but it's only for triggers, |
|
1108 |
# it's not a real interactive action. |
|
1109 |
continue |
|
1106 | 1110 |
with get_publisher().substitutions.temporary_feed(self): |
1107 | 1111 |
if not item.check_condition(self, **(condition_kwargs or {})): |
1108 | 1112 |
continue |
wcs/wf/jump.py | ||
---|---|---|
27 | 27 |
from quixote.html import htmltext |
28 | 28 | |
29 | 29 |
from wcs.api import get_user_from_api_query_string, is_url_signed |
30 |
from wcs.conditions import Condition |
|
31 | 30 |
from wcs.workflows import Workflow, WorkflowGlobalAction, WorkflowStatusJumpItem, register_item_class |
32 | 31 | |
33 | 32 |
from ..qommon import _, errors, force_str |
... | ... | |
85 | 84 |
pass |
86 | 85 |
elif not item.check_auth(self.formdata, user): |
87 | 86 |
raise errors.AccessForbiddenError() |
88 |
if item.must_jump(self.formdata, trigger=item.trigger):
|
|
87 |
if item.check_condition(self.formdata, trigger=component):
|
|
89 | 88 |
workflow_data = None |
90 | 89 |
if hasattr(get_request(), '_json'): |
91 | 90 |
workflow_data = get_request().json |
... | ... | |
249 | 248 |
return None |
250 | 249 | |
251 | 250 |
def perform(self, formdata): |
252 |
if not self.status: |
|
253 |
return |
|
254 | ||
255 |
if self.must_jump(formdata): |
|
256 |
wf_status = self.get_target_status(formdata) |
|
257 |
if wf_status: |
|
258 |
self.handle_markers_stack(formdata) |
|
259 |
formdata.status = 'wf-%s' % wf_status[0].id |
|
260 | ||
261 |
def check_condition(self, formdata, *args, **kwargs): |
|
262 |
# ship condition check here so it is not evaluated twice. |
|
263 |
return True |
|
264 | ||
265 |
def must_jump(self, formdata, trigger=None): |
|
266 |
must_jump = True |
|
251 |
wf_status = self.get_target_status(formdata) |
|
252 |
if wf_status: |
|
253 |
self.handle_markers_stack(formdata) |
|
254 |
formdata.status = 'wf-%s' % wf_status[0].id |
|
267 | 255 | |
268 |
if self.condition: |
|
269 |
context = {'formdata': formdata, 'status_item': self} |
|
270 |
try: |
|
271 |
must_jump = Condition(self.condition, context).evaluate() |
|
272 |
except RuntimeError: |
|
273 |
must_jump = False |
|
274 | ||
275 |
if self.trigger: |
|
276 |
triggered = trigger is not None and trigger == self.trigger |
|
277 |
must_jump = must_jump and triggered |
|
256 |
def check_condition(self, formdata, *args, trigger=None, **kwargs): |
|
257 |
result = super().check_condition(formdata, *args, **kwargs) |
|
258 |
if not result: |
|
259 |
return False |
|
278 | 260 | |
279 | 261 |
if self.timeout: |
280 | 262 |
timeout_str = self.compute(self.timeout) |
... | ... | |
294 | 276 |
notify=False, |
295 | 277 |
record=True, |
296 | 278 |
) |
297 |
must_jump = False
|
|
279 |
return False
|
|
298 | 280 |
last = formdata.last_update_time |
299 | 281 |
if last and timeout_seconds: |
300 | 282 |
diff = time.time() - time.mktime(last) |
301 |
must_jump = (diff > timeout_seconds) and must_jump |
|
283 |
if diff < timeout_seconds: |
|
284 |
return False |
|
302 | 285 | |
303 |
return must_jump |
|
286 |
if self.trigger: |
|
287 |
if trigger is None or trigger != self.trigger: |
|
288 |
return False |
|
289 | ||
290 |
return True |
|
304 | 291 | |
305 | 292 | |
306 | 293 |
register_item_class(JumpWorkflowStatusItem) |
... | ... | |
376 | 363 |
get_publisher().substitutions.feed(get_publisher()) |
377 | 364 |
get_publisher().substitutions.feed(formdef) |
378 | 365 |
get_publisher().substitutions.feed(formdata) |
379 |
if jump_action.must_jump(formdata):
|
|
366 |
if jump_action.check_condition(formdata):
|
|
380 | 367 |
jump_and_perform(formdata, jump_action, event=('timeout-jump', jump_action.id)) |
381 | 368 |
break |
382 | 369 | |
383 |
- |