clear rest_path __unique cache in clear_all_caches, fix related test
"""workflow views:* IWorkflowable views and forms* workflow entities views (State, Transition, TrInfo):organization: Logilab:copyright: 2001-2010 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"_=unicodefromlogilab.mtconverterimportxml_escapefromlogilab.common.graphimportescape,GraphGenerator,DotBackendfromcubicwebimportUnauthorized,viewfromcubicweb.selectorsimport(implements,has_related_entities,one_line_rset,relation_possible,match_form_params,implements,score_entity)fromcubicweb.interfacesimportIWorkflowablefromcubicweb.viewimportEntityViewfromcubicweb.schemaimportdisplay_namefromcubicweb.webimportuicfg,stdmsgs,action,component,form,actionfromcubicweb.webimportformfieldsasff,formwidgetsasfwdgsfromcubicweb.web.viewsimportTmpFileViewMixin,forms,primary,autoform_pvs=uicfg.primaryview_section_pvs.tag_subject_of(('Workflow','initial_state','*'),'hidden')_pvs.tag_object_of(('*','state_of','Workflow'),'hidden')_pvs.tag_object_of(('*','transition_of','Workflow'),'hidden')_abaa=uicfg.actionbox_appearsin_addmenu_abaa.tag_subject_of(('BaseTransition','condition','RQLExpression'),False)_abaa.tag_subject_of(('State','allowed_transition','BaseTransition'),False)_abaa.tag_object_of(('SubWorkflowExitPoint','destination_state','State'),False)_abaa.tag_object_of(('State','state_of','Workflow'),True)_abaa.tag_object_of(('Transition','transition_of','Workflow'),True)_abaa.tag_object_of(('WorkflowTransition','transition_of','Workflow'),True)_afs=uicfg.autoform_section_afs.tag_subject_of(('TrInfo','to_state','*'),'main','hidden')_afs.tag_subject_of(('TrInfo','from_state','*'),'main','hidden')_afs.tag_object_of(('State','allowed_transition','*'),'main','attributes')# IWorkflowable views #########################################################classChangeStateForm(forms.CompositeEntityForm):__regid__='changestate'form_renderer_id='base'# don't want EntityFormRendererform_buttons=[fwdgs.SubmitButton(),fwdgs.Button(stdmsgs.BUTTON_CANCEL,cwaction='cancel')]classChangeStateFormView(form.FormViewMixIn,view.EntityView):__regid__='statuschange'title=_('status change')__select__=(one_line_rset()&implements(IWorkflowable)&match_form_params('treid'))defcell_call(self,row,col):entity=self.cw_rset.get_entity(row,col)transition=self._cw.entity_from_eid(self._cw.form['treid'])form=self.get_form(entity,transition)self.w(u'<h4>%s%s</h4>\n'%(self._cw._(transition.name),entity.view('oneline')))msg=self.req._('status will change from %(st1)s to %(st2)s')%{'st1':entity.printable_state,'st2':self._cw._(transition.destination().name)}self.w(u'<p>%s</p>\n'%msg)self.w(form.render())defredirectpath(self,entity):returnentity.rest_path()defget_form(self,entity,transition,**kwargs):# XXX used to specify both rset/row/col and entity in case implements# selector (and not implements) is used on custom formform=self._cw.vreg['forms'].select('changestate',self._cw,entity=entity,transition=transition,redirect_path=self.redirectpath(entity),**kwargs)trinfo=self._cw.vreg['etypes'].etype_class('TrInfo')(self._cw)trinfo.eid=self._cw.varmaker.next()subform=self._cw.vreg['forms'].select('edition',self._cw,entity=trinfo,mainform=False)subform.field_by_name('wf_info_for','subject').value=entity.eidtrfield=subform.field_by_name('by_transition','subject')trfield.widget=fwdgs.HiddenInput()trfield.value=transition.eidform.add_subform(subform)returnformclassWFHistoryView(EntityView):__regid__='wfhistory'__select__=relation_possible('wf_info_for',role='object')& \score_entity(lambdax:x.workflow_history)title=_('Workflow history')defcell_call(self,row,col,view=None):_=self._cw._eid=self.cw_rset[row][col]sel='Any FS,TS,WF,D'rql=' ORDERBY D DESC WHERE WF wf_info_for X,'\'WF from_state FS, WF to_state TS, WF comment C,'\'WF creation_date D'ifself._cw.vreg.schema.eschema('CWUser').has_perm(self._cw,'read'):sel+=',U,C'rql+=', WF owned_by U?'displaycols=range(5)headers=(_('from_state'),_('to_state'),_('comment'),_('date'),_('CWUser'))else:sel+=',C'displaycols=range(4)headers=(_('from_state'),_('to_state'),_('comment'),_('date'))rql='%s%s, X eid %%(x)s'%(sel,rql)try:rset=self._cw.execute(rql,{'x':eid},'x')exceptUnauthorized:returnifrset:self.wview('table',rset,title=_(self.title),displayactions=False,displaycols=displaycols,headers=headers)classWFHistoryVComponent(component.EntityVComponent):"""display the workflow history for entities supporting it"""__regid__='wfhistory'__select__=WFHistoryView.__select__&component.EntityVComponent.__select__context='navcontentbottom'title=_('Workflow history')defcell_call(self,row,col,view=None):self.wview('wfhistory',self.cw_rset,row=row,col=col,view=view)# workflow actions #############################################################classWorkflowActions(action.Action):"""fill 'workflow' sub-menu of the actions box"""__regid__='workflow'__select__=(action.Action.__select__&one_line_rset()&relation_possible('in_state'))submenu=_('workflow')order=10deffill_menu(self,box,menu):entity=self.cw_rset.get_entity(self.cw_rowor0,self.cw_color0)menu.label=u'%s: %s'%(self._cw._('state'),entity.printable_state)menu.append_anyway=Truesuper(WorkflowActions,self).fill_menu(box,menu)defactual_actions(self):entity=self.cw_rset.get_entity(self.cw_rowor0,self.cw_color0)hastr=Falsefortrinentity.possible_transitions():url=entity.absolute_url(vid='statuschange',treid=tr.eid)yieldself.build_action(self._cw._(tr.name),url)hastr=True# don't propose to see wf if user can't pass any transitionifhastr:wfurl=entity.current_workflow.absolute_url()yieldself.build_action(self._cw._('view workflow'),wfurl)ifentity.workflow_history:wfurl=entity.absolute_url(vid='wfhistory')yieldself.build_action(self._cw._('view history'),wfurl)# workflow entity types views ##################################################_pvs=uicfg.primaryview_section_pvs.tag_subject_of(('Workflow','initial_state','*'),'hidden')_pvs.tag_object_of(('*','state_of','Workflow'),'hidden')_pvs.tag_object_of(('*','transition_of','Workflow'),'hidden')_abaa=uicfg.actionbox_appearsin_addmenu_abaa.tag_subject_of(('BaseTransition','condition','RQLExpression'),False)_abaa.tag_subject_of(('State','allowed_transition','BaseTransition'),False)_abaa.tag_object_of(('SubWorkflowExitPoint','destination_state','State'),False)_abaa.tag_object_of(('State','state_of','Workflow'),True)_abaa.tag_object_of(('BaseTransition','transition_of','Workflow'),False)_abaa.tag_object_of(('Transition','transition_of','Workflow'),True)_abaa.tag_object_of(('WorkflowTransition','transition_of','Workflow'),True)classWorkflowPrimaryView(primary.PrimaryView):__select__=implements('Workflow')defrender_entity_attributes(self,entity):self.w(entity.view('reledit',rtype='description'))self.w(u'<img src="%s" alt="%s"/>'%(xml_escape(entity.absolute_url(vid='wfgraph')),xml_escape(self._cw._('graphical workflow for %s')%entity.name)))classCellView(view.EntityView):__regid__='cell'__select__=implements('TrInfo')defcell_call(self,row,col,cellvid=None):self.w(self.cw_rset.get_entity(row,col).view('reledit',rtype='comment'))classStateInContextView(view.EntityView):"""convenience trick, State's incontext view should not be clickable"""__regid__='incontext'__select__=implements('State')defcell_call(self,row,col):self.w(xml_escape(self._cw.view('textincontext',self.cw_rset,row=row,col=col)))# workflow entity types edition ################################################_afs=uicfg.autoform_section_afs.tag_subject_of(('TrInfo','to_state','*'),'main','hidden')_afs.tag_subject_of(('TrInfo','from_state','*'),'main','hidden')_afs.tag_object_of(('State','allowed_transition','*'),'main','attributes')_afs.tag_subject_of(('State','allowed_transition','*'),'main','attributes')defworkflow_items_for_relation(req,wfeid,wfrelation,targetrelation):wf=req.entity_from_eid(wfeid)rschema=req.vreg.schema[targetrelation]returnsorted((e.view('combobox'),e.eid)foreingetattr(wf,'reverse_%s'%wfrelation)ifrschema.has_perm(req,'add',toeid=e.eid))classTransitionEditionForm(autoform.AutomaticEntityForm):__select__=implements('Transition')defworkflow_states_for_relation(self,targetrelation):eids=self.edited_entity.linked_to('transition_of','subject')ifeids:returnworkflow_items_for_relation(self._cw,eids[0],'state_of',targetrelation)return[]defsubject_destination_state_vocabulary(self,rtype,limit=None):ifnotself.edited_entity.has_eid():returnself.workflow_states_for_relation('destination_state')returnself.subject_relation_vocabulary(rtype,limit)defobject_allowed_transition_vocabulary(self,rtype,limit=None):ifnotself.edited_entity.has_eid():returnself.workflow_states_for_relation('allowed_transition')returnself.object_relation_vocabulary(rtype,limit)classStateEditionForm(autoform.AutomaticEntityForm):__select__=implements('State')defsubject_allowed_transition_vocabulary(self,rtype,limit=None):ifnotself.edited_entity.has_eid():eids=self.edited_entity.linked_to('state_of','subject')ifeids:returnworkflow_items_for_relation(self._cw,eids[0],'transition_of','allowed_transition')return[]# workflow images ##############################################################classWorkflowDotPropsHandler(object):def__init__(self,req):self._=req._defnode_properties(self,stateortransition):"""return default DOT drawing options for a state or transition"""props={'label':stateortransition.printable_value('name'),'fontname':'Courier'}ifhasattr(stateortransition,'state_of'):props['shape']='box'props['style']='filled'ifstateortransition.reverse_initial_state:props['color']='#88CC88'else:props['shape']='ellipse'descr=[]tr=stateortransitioniftr.require_group:descr.append('%s%s'%(self._('groups:'),','.join(g.printable_value('name')forgintr.require_group)))iftr.condition:descr.append('%s%s'%(self._('condition:'),' | '.join(e.expressionforeintr.condition)))ifdescr:props['label']+=escape('\n'.join(descr))returnpropsdefedge_properties(self,transition,fromstate,tostate):return{'label':'','dir':'forward','color':'black','style':'filled'}classWorkflowVisitor:def__init__(self,entity):self.entity=entitydefnodes(self):forstateinself.entity.reverse_state_of:state.complete()yieldstate.eid,statefortransitioninself.entity.reverse_transition_of:transition.complete()yieldtransition.eid,transitiondefedges(self):fortransitioninself.entity.reverse_transition_of:forincomingstateintransition.reverse_allowed_transition:yieldincomingstate.eid,transition.eid,transitionyieldtransition.eid,transition.destination().eid,transitionclassWorkflowImageView(TmpFileViewMixin,view.EntityView):__regid__='wfgraph'__select__=implements('Workflow')content_type='image/png'def_generate(self,tmpfile):"""display schema information for an entity"""entity=self.cw_rset.get_entity(self.cw_row,self.cw_col)visitor=WorkflowVisitor(entity)prophdlr=WorkflowDotPropsHandler(self._cw)generator=GraphGenerator(DotBackend('workflow','LR',ratio='compress',size='30,12'))returngenerator.generate(visitor,prophdlr,tmpfile)