451 # then check it has a state set |
454 # then check it has a state set |
452 fromstate = forentity.current_state |
455 fromstate = forentity.current_state |
453 if fromstate is None: |
456 if fromstate is None: |
454 msg = session._('related entity has no state') |
457 msg = session._('related entity has no state') |
455 raise ValidationError(entity.eid, {None: msg}) |
458 raise ValidationError(entity.eid, {None: msg}) |
|
459 # True if we are coming back from subworkflow |
|
460 swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None) |
|
461 cowpowers = session.is_super_session or 'managers' in session.user.groups |
456 # no investigate the requested state change... |
462 # no investigate the requested state change... |
457 try: |
463 try: |
458 treid = entity['by_transition'] |
464 treid = entity['by_transition'] |
459 except KeyError: |
465 except KeyError: |
460 # no transition set, check user is a manager and destination state is |
466 # no transition set, check user is a manager and destination state is |
461 # specified (and valid) |
467 # specified (and valid) |
462 if not (session.is_super_session or 'managers' in session.user.groups): |
468 if not cowpowers: |
463 msg = session._('mandatory relation') |
469 msg = session._('mandatory relation') |
464 raise ValidationError(entity.eid, {'by_transition': msg}) |
470 raise ValidationError(entity.eid, {'by_transition': msg}) |
465 deststateeid = entity.get('to_state') |
471 deststateeid = entity.get('to_state') |
466 if not deststateeid: |
472 if not deststateeid: |
467 msg = session._('mandatory relation') |
473 msg = session._('mandatory relation') |
468 raise ValidationError(entity.eid, {'by_transition': msg}) |
474 raise ValidationError(entity.eid, {'by_transition': msg}) |
469 deststate = wf.state_by_eid(deststateeid) |
475 deststate = wf.state_by_eid(deststateeid) |
470 if deststate is None: |
476 if not cowpowers and deststate is None: |
471 msg = session._("state doesn't belong to entity's workflow") |
477 msg = entity.req._("state doesn't belong to entity's workflow") |
472 raise ValidationError(entity.eid, {'to_state': msg}) |
478 raise ValidationError(entity.eid, {'to_state': msg}) |
473 else: |
479 else: |
474 # check transition is valid and allowed |
480 # check transition is valid and allowed, unless we're coming back from |
475 tr = wf.transition_by_eid(treid) |
481 # subworkflow |
476 if tr is None: |
482 tr = session.entity_from_eid(treid) |
477 msg = session._("transition doesn't belong to entity's workflow") |
483 if swtr is None: |
478 raise ValidationError(entity.eid, {'by_transition': msg}) |
484 if tr is None: |
479 if not tr.has_input_state(fromstate): |
485 msg = session._("transition doesn't belong to entity's workflow") |
480 msg = session._("transition isn't allowed") |
486 raise ValidationError(entity.eid, {'by_transition': msg}) |
481 raise ValidationError(entity.eid, {'by_transition': msg}) |
487 if not tr.has_input_state(fromstate): |
482 if not tr.may_be_fired(foreid): |
488 msg = session._("transition isn't allowed") |
483 msg = session._("transition may not be fired") |
489 raise ValidationError(entity.eid, {'by_transition': msg}) |
484 raise ValidationError(entity.eid, {'by_transition': msg}) |
490 if not tr.may_be_fired(foreid): |
485 deststateeid = tr.destination().eid |
491 msg = session._("transition may not be fired") |
|
492 raise ValidationError(entity.eid, {'by_transition': msg}) |
|
493 if entity.get('to_state'): |
|
494 deststateeid = entity['to_state'] |
|
495 if not cowpowers and deststateeid != tr.destination().eid: |
|
496 msg = session._("transition isn't allowed") |
|
497 raise ValidationError(entity.eid, {'by_transition': msg}) |
|
498 if swtr is None: |
|
499 deststate = session.entity_from_eid(deststateeid) |
|
500 if not cowpowers and deststate is None: |
|
501 msg = entity.req._("state doesn't belong to entity's workflow") |
|
502 raise ValidationError(entity.eid, {'to_state': msg}) |
|
503 else: |
|
504 deststateeid = tr.destination().eid |
486 # everything is ok, add missing information on the trinfo entity |
505 # everything is ok, add missing information on the trinfo entity |
487 entity['from_state'] = fromstate.eid |
506 entity['from_state'] = fromstate.eid |
488 entity['to_state'] = deststateeid |
507 entity['to_state'] = deststateeid |
489 nocheck = session.transaction_data.setdefault('skip-security', set()) |
508 nocheck = session.transaction_data.setdefault('skip-security', set()) |
490 nocheck.add((entity.eid, 'from_state', fromstate.eid)) |
509 nocheck.add((entity.eid, 'from_state', fromstate.eid)) |
491 nocheck.add((entity.eid, 'to_state', deststateeid)) |
510 nocheck.add((entity.eid, 'to_state', deststateeid)) |
492 |
511 |
493 |
|
494 def after_add_trinfo(session, entity): |
512 def after_add_trinfo(session, entity): |
495 """change related entity state""" |
513 """change related entity state""" |
496 _change_state(session, entity['wf_info_for'], |
514 _change_state(session, entity['wf_info_for'], |
497 entity['from_state'], entity['to_state']) |
515 entity['from_state'], entity['to_state']) |
|
516 forentity = session.entity_from_eid(entity['wf_info_for']) |
|
517 assert forentity.current_state.eid == entity['to_state'] |
|
518 if forentity.main_workflow.eid != forentity.current_workflow.eid: |
|
519 # we're in a subworkflow, check if we've reached an exit point |
|
520 wftr = forentity.subworkflow_input_transition() |
|
521 if wftr is None: |
|
522 # inconsistency detected |
|
523 msg = entity.req._("state doesn't belong to entity's current workflow") |
|
524 raise ValidationError(entity.eid, {'to_state': msg}) |
|
525 tostate = wftr.get_exit_point(entity['to_state']) |
|
526 if tostate is not None: |
|
527 # reached an exit point |
|
528 msg = session._('exiting from subworkflow %s') |
|
529 msg %= session._(forentity.current_workflow.name) |
|
530 session.transaction_data[(forentity.eid, 'subwfentrytr')] = True |
|
531 # XXX iirk |
|
532 req = forentity.req |
|
533 forentity.req = session.super_session |
|
534 try: |
|
535 trinfo = forentity.change_state(tostate, msg, u'text/plain', |
|
536 tr=wftr) |
|
537 finally: |
|
538 forentity.req = req |
498 |
539 |
499 |
540 |
500 class SetInitialStateOp(PreCommitOperation): |
541 class SetInitialStateOp(PreCommitOperation): |
501 """make initial state be a default state""" |
542 """make initial state be a default state""" |
502 |
543 |
518 def set_initial_state_after_add(session, entity): |
559 def set_initial_state_after_add(session, entity): |
519 SetInitialStateOp(session, entity=entity) |
560 SetInitialStateOp(session, entity=entity) |
520 |
561 |
521 |
562 |
522 def before_add_in_state(session, eidfrom, rtype, eidto): |
563 def before_add_in_state(session, eidfrom, rtype, eidto): |
523 """check state apply""" |
564 """check state apply, in case of direct in_state change using unsafe_execute |
|
565 """ |
524 nocheck = session.transaction_data.setdefault('skip-security', ()) |
566 nocheck = session.transaction_data.setdefault('skip-security', ()) |
525 if (eidfrom, 'in_state', eidto) in nocheck: |
567 if (eidfrom, 'in_state', eidto) in nocheck: |
526 # state changed through TrInfo insertion, so we already know it's ok |
568 # state changed through TrInfo insertion, so we already know it's ok |
527 print 'skip in_state check' |
|
528 return |
569 return |
529 entity = session.entity_from_eid(eidfrom) |
570 entity = session.entity_from_eid(eidfrom) |
530 mainwf = entity.main_workflow |
571 mainwf = entity.main_workflow |
531 if mainwf is None: |
572 if mainwf is None: |
532 msg = session._('entity has no workflow set') |
573 msg = session._('entity has no workflow set') |
536 break |
577 break |
537 else: |
578 else: |
538 msg = session._("state doesn't belong to entity's workflow. You may " |
579 msg = session._("state doesn't belong to entity's workflow. You may " |
539 "want to set a custom workflow for this entity first.") |
580 "want to set a custom workflow for this entity first.") |
540 raise ValidationError(eidfrom, {'in_state': msg}) |
581 raise ValidationError(eidfrom, {'in_state': msg}) |
|
582 if entity.current_workflow and wf.eid != entity.current_workflow.eid: |
|
583 msg = session._("state doesn't belong to entity's current workflow") |
|
584 raise ValidationError(eidfrom, {'in_state': msg}) |
|
585 |
|
586 |
|
587 class CheckTrExitPoint(PreCommitOperation): |
|
588 |
|
589 def precommit_event(self): |
|
590 tr = self.session.entity_from_eid(self.treid) |
|
591 outputs = set() |
|
592 for ep in tr.subworkflow_exit: |
|
593 if ep.subwf_state.eid in outputs: |
|
594 msg = self.session._("can't have multiple exits on the same state") |
|
595 raise ValidationError(self.treid, {'subworkflow_exit': msg}) |
|
596 outputs.add(ep.subwf_state.eid) |
|
597 |
|
598 |
|
599 def after_add_subworkflow_exit(session, eidfrom, rtype, eidto): |
|
600 CheckTrExitPoint(session, treid=eidfrom) |
541 |
601 |
542 |
602 |
543 class WorkflowChangedOp(PreCommitOperation): |
603 class WorkflowChangedOp(PreCommitOperation): |
544 """fix entity current state when changing its workflow""" |
604 """fix entity current state when changing its workflow""" |
545 |
605 |