server/hooks.py
branch3.5
changeset 2992 a5b8bf107a1a
parent 2985 79185b3ccf2c
child 3039 7d5a4d27d052
equal deleted inserted replaced
2991:dab951c08896 2992:a5b8bf107a1a
   416     hm.register_hook(after_del_in_group, 'after_delete_relation', 'in_group')
   416     hm.register_hook(after_del_in_group, 'after_delete_relation', 'in_group')
   417 
   417 
   418 
   418 
   419 # workflow handling ###########################################################
   419 # workflow handling ###########################################################
   420 
   420 
       
   421 from cubicweb.entities.wfobjs import WorkflowTransition, WorkflowException
       
   422 
   421 def _change_state(session, x, oldstate, newstate):
   423 def _change_state(session, x, oldstate, newstate):
   422     nocheck = session.transaction_data.setdefault('skip-security', set())
   424     nocheck = session.transaction_data.setdefault('skip-security', set())
   423     nocheck.add((x, 'in_state', oldstate))
   425     nocheck.add((x, 'in_state', oldstate))
   424     nocheck.add((x, 'in_state', newstate))
   426     nocheck.add((x, 'in_state', newstate))
   425     # delete previous state first in case we're using a super session
   427     # delete previous state first in case we're using a super session
   437         foreid = entity['wf_info_for']
   439         foreid = entity['wf_info_for']
   438     except KeyError:
   440     except KeyError:
   439         msg = session._('mandatory relation')
   441         msg = session._('mandatory relation')
   440         raise ValidationError(entity.eid, {'wf_info_for': msg})
   442         raise ValidationError(entity.eid, {'wf_info_for': msg})
   441     forentity = session.entity_from_eid(foreid)
   443     forentity = session.entity_from_eid(foreid)
   442     # then check it has a workflow set
   444     # then check it has a workflow set, unless we're in the process of changing
       
   445     # entity's workflow
   443     if session.transaction_data.get((forentity.eid, 'customwf')):
   446     if session.transaction_data.get((forentity.eid, 'customwf')):
   444         wfeid = session.transaction_data[(forentity.eid, 'customwf')]
   447         wfeid = session.transaction_data[(forentity.eid, 'customwf')]
   445         wf = session.entity_from_eid(wfeid)
   448         wf = session.entity_from_eid(wfeid)
   446     else:
   449     else:
   447         wf = forentity.current_workflow
   450         wf = forentity.current_workflow
   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 
   569                                   entity.current_state.eid, deststate.eid)
   629                                   entity.current_state.eid, deststate.eid)
   570                 return
   630                 return
   571             msg = session._('workflow changed to "%s"')
   631             msg = session._('workflow changed to "%s"')
   572             msg %= session._(mainwf.name)
   632             msg %= session._(mainwf.name)
   573             session.transaction_data[(entity.eid, 'customwf')] = self.wfeid
   633             session.transaction_data[(entity.eid, 'customwf')] = self.wfeid
   574             entity.change_state(deststate, msg)
   634             entity.change_state(deststate, msg, u'text/plain')
   575 
   635 
   576 
   636 
   577 def set_custom_workflow(session, eidfrom, rtype, eidto):
   637 def set_custom_workflow(session, eidfrom, rtype, eidto):
   578     WorkflowChangedOp(session, eid=eidfrom, wfeid=eidto)
   638     WorkflowChangedOp(session, eid=eidfrom, wfeid=eidto)
   579 
   639 
   603                                  str(eschema))
   663                                  str(eschema))
   604         hm.register_hook(set_custom_workflow, 'after_add_relation', 'custom_workflow')
   664         hm.register_hook(set_custom_workflow, 'after_add_relation', 'custom_workflow')
   605         hm.register_hook(del_custom_workflow, 'after_delete_relation', 'custom_workflow')
   665         hm.register_hook(del_custom_workflow, 'after_delete_relation', 'custom_workflow')
   606         hm.register_hook(after_del_workflow, 'after_delete_entity', 'Workflow')
   666         hm.register_hook(after_del_workflow, 'after_delete_entity', 'Workflow')
   607         hm.register_hook(before_add_in_state, 'before_add_relation', 'in_state')
   667         hm.register_hook(before_add_in_state, 'before_add_relation', 'in_state')
       
   668         hm.register_hook(after_add_subworkflow_exit, 'after_add_relation', 'subworkflow_exit')
   608 
   669 
   609 
   670 
   610 # CWProperty hooks #############################################################
   671 # CWProperty hooks #############################################################
   611 
   672 
   612 
   673