"""Core hooks: workflow related hooks:organization: Logilab:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""__docformat__="restructuredtext en"fromdatetimeimportdatetimefromcubicwebimportRepositoryError,ValidationErrorfromcubicweb.interfacesimportIWorkflowablefromcubicweb.selectorsimportentity_implementsfromcubicweb.serverimporthookdefprevious_state(session,eid):"""return the state of the entity with the given eid, usually since it's changing in the current transaction. Due to internal relation hooks, the relation may has been deleted at this point, so we have handle that """ifsession.added_in_transaction(eid):returnpending=session.transaction_data.get('pendingrelations',())foreidfrom,rtype,eidtoinreversed(pending):ifrtype=='in_state'andeidfrom==eid:rset=session.execute('Any S,N WHERE S eid %(x)s, S name N',{'x':eidto},'x')returnrset.get_entity(0,0)rset=session.execute('Any S,N WHERE X eid %(x)s, X in_state S, S name N',{'x':eid},'x')ifrset:returnrset.get_entity(0,0)defrelation_deleted(session,eidfrom,rtype,eidto):session.transaction_data.setdefault('pendingrelations',[]).append((eidfrom,rtype,eidto))class_SetInitialStateOp(hook.Operation):"""make initial state be a default state"""defprecommit_event(self):session=self.sessionentity=self.entity# if there is an initial state and the entity's state is not set,# use the initial state as a default stateifnotsession.deleted_in_transaction(entity.eid)andnotentity.in_state:rset=session.execute('Any S WHERE ET initial_state S, ET name %(name)s',{'name':entity.id})ifrset:session.add_relation(entity.eid,'in_state',rset[0][0])classWorkflowHook(hook.Hook):__abstract__=Truecategory='worfklow'classSetInitialStateHook(WorkflowHook):__id__='wfsetinitial'__select__=WorkflowHook.__select__&entity_implements(IWorkflowable)events=('after_add_entity',)def__call__(self):_SetInitialStateOp(self._cw,entity=self.entity)classPrepareStateChangeHook(WorkflowHook):"""record previous state information"""__id__='cwdelstate'__select__=WorkflowHook.__select__&hook.match_rtype('in_state')events=('before_delete_relation',)def__call__(self):self._cw.transaction_data.setdefault('pendingrelations',[]).append((self.eidfrom,self.rtype,self.eidto))classFireTransitionHook(PrepareStateChangeHook):"""check the transition is allowed and record transition information"""__id__='wffiretransition'events=('before_add_relation',)def__call__(self):session=self._cweidfrom=self.eidfromeidto=self.eidtostate=previous_state(session,eidfrom)etype=session.describe(eidfrom)[0]ifnot(session.is_super_sessionor'managers'insession.user.groups):ifnotstateisNone:entity=session.entity_from_eid(eidfrom)# we should find at least one transition going to this statetry:iter(state.transitions(entity,eidto)).next()exceptStopIteration:msg=session._('transition is not allowed')raiseValidationError(eidfrom,{'in_state':msg})else:# not a transition# check state is initial state if the workflow defines oneisrset=session.unsafe_execute('Any S WHERE ET initial_state S, ET name %(etype)s',{'etype':etype})ifisrsetandnoteidto==isrset[0][0]:msg=session._('not the initial state for this entity')raiseValidationError(eidfrom,{'in_state':msg})eschema=session.repo.schema[etype]ifnot'wf_info_for'ineschema.object_relations():# workflow history not activated for this entity typereturnrql='INSERT TrInfo T: T wf_info_for E, T to_state DS, T comment %(comment)s'args={'comment':session.get_shared_data('trcomment',None,pop=True),'e':eidfrom,'ds':eidto}cformat=session.get_shared_data('trcommentformat',None,pop=True)ifcformatisnotNone:args['comment_format']=cformatrql+=', T comment_format %(comment_format)s'restriction=['DS eid %(ds)s, E eid %(e)s']ifnotstateisNone:# not a transitionrql+=', T from_state FS'restriction.append('FS eid %(fs)s')args['fs']=state.eidrql='%s WHERE %s'%(rql,', '.join(restriction))session.unsafe_execute(rql,args,'e')classSetModificationDateOnStateChange(WorkflowHook):"""update entity's modification date after changing its state"""__id__='wfsyncmdate'__select__=WorkflowHook.__select__&hook.match_rtype('in_state')events=('after_add_relation',)def__call__(self):ifself._cw.added_in_transaction(self.eidfrom):# new entity, not neededreturnentity=self._cw.entity_from_eid(self.eidfrom)try:entity.set_attributes(modification_date=datetime.now())exceptRepositoryError,ex:# usually occurs if entity is coming from a read-only source# (eg ldap user)self.warning('cant change modification date for %s: %s',entity,ex)