Back to index

plone3  3.1.7
WorkflowTool.py
Go to the documentation of this file.
00001 from zope.component import getMultiAdapter
00002 from Products.CMFCore.interfaces import IConfigurableWorkflowTool
00003 
00004 from Products.CMFCore.utils import getToolByName
00005 from Products.CMFCore.WorkflowTool import WorkflowTool as BaseTool
00006 from Products.CMFPlone import ToolNames
00007 from Products.CMFPlone.utils import base_hasattr
00008 from Products.CMFPlone.interfaces import IWorkflowChain
00009 from ZODB.POSException import ConflictError
00010 from Acquisition import aq_base, aq_parent, aq_inner
00011 
00012 from Globals import InitializeClass
00013 from AccessControl import getSecurityManager, ClassSecurityInfo
00014 from Products.CMFCore.permissions import ManagePortal
00015 from Products.DCWorkflow.Transitions import TRIGGER_USER_ACTION
00016 from Products.CMFPlone.PloneBaseTool import PloneBaseTool
00017 
00018 
00019 class WorkflowTool(PloneBaseTool, BaseTool):
00020 
00021     meta_type = ToolNames.WorkflowTool
00022     security = ClassSecurityInfo()
00023     plone_tool = 1
00024     toolicon = 'skins/plone_images/workflow_icon.gif'
00025 
00026     __implements__ = (PloneBaseTool.__implements__, BaseTool.__implements__, )
00027 
00028     # TODO this should not make it into 1.0
00029     # Refactor me, my maker was tired
00030     def flattenTransitions(self, objs, container=None):
00031         """ this is really hokey - hold on!!"""
00032         if hasattr(objs, 'startswith'):
00033             return ()
00034 
00035         # TODO Need to behave differently for paths
00036         if len(objs) and '/' in objs[0]:
00037             return self.flattenTransitionsForPaths(objs)
00038         transitions=[]
00039         t_names=[]
00040 
00041         if container is None:
00042             container = self
00043         for o in [getattr(container, oid, None) for oid in objs]:
00044             trans=()
00045             try:
00046                 trans=self.getTransitionsFor(o, container)
00047             except ConflictError:
00048                 raise
00049             except:
00050                 pass
00051             if trans:
00052                 for t in trans:
00053                     if t['name'] not in t_names:
00054                         transitions.append(t)
00055                         t_names.append(t['name'])
00056 
00057         return tuple(transitions[:])
00058 
00059 
00060     def flattenTransitionsForPaths(self, paths):
00061         """ this is even more hokey!!"""
00062         if hasattr(paths, 'startswith'):
00063             return ()
00064 
00065         transitions=[]
00066         t_names=[]
00067         portal = getToolByName(self, 'portal_url').getPortalObject()
00068 
00069         for o in [portal.restrictedTraverse(path) for path in paths]:
00070             trans=()
00071             try:
00072                 trans=self.getTransitionsFor(o, o.aq_inner.aq_parent)
00073             except ConflictError:
00074                 raise
00075             except:
00076                 pass
00077             if trans:
00078                 for t in trans:
00079                     if t['name'] not in t_names:
00080                         transitions.append(t)
00081                         t_names.append(t['name'])
00082 
00083         return tuple(transitions[:])
00084 
00085     security.declarePublic('getTransitionsFor')
00086     def getTransitionsFor(self, obj=None, container=None, REQUEST=None):
00087         if type(obj) is type([]):
00088             return self.flattenTransitions(objs=obj, container=container)
00089         result = {}
00090         chain = self.getChainFor(obj)
00091         for wf_id in chain:
00092             wf = self.getWorkflowById(wf_id)
00093             if wf is not None:
00094                 sdef = wf._getWorkflowStateOf(obj)
00095                 if sdef is not None:
00096                     for tid in sdef.transitions:
00097                         tdef = wf.transitions.get(tid, None)
00098                         if tdef is not None and \
00099                            tdef.trigger_type == TRIGGER_USER_ACTION and \
00100                            tdef.actbox_name and \
00101                            wf._checkTransitionGuard(tdef, obj) and \
00102                            not result.has_key(tdef.id):
00103                             result[tdef.id] = {
00104                                     'id': tdef.id,
00105                                     'title': tdef.title,
00106                                     'title_or_id': tdef.title_or_id(),
00107                                     'description': tdef.description,
00108                                     'name': tdef.actbox_name,
00109                                     'url': tdef.actbox_url %
00110                                            {'content_url': obj.absolute_url(),
00111                                             'portal_url' : '',
00112                                             'folder_url' : ''}}
00113         return tuple(result.values())
00114 
00115     def workflows_in_use(self):
00116         """ gathers all the available workflow chains (sequence of workflow ids, ).  """
00117         in_use = []
00118 
00119         in_use.append( self._default_chain )
00120 
00121         if self._chains_by_type:
00122             for chain in self._chains_by_type.values():
00123                 in_use.append(chain)
00124 
00125         return tuple(in_use[:])
00126 
00127     security.declarePublic('getWorklists')
00128     def getWorklists(self):
00129         """ instead of manually scraping actions_box, lets:
00130             query for all worklists in all workflow definitions.
00131             Returns a dictionary whos value is sequence of dictionaries
00132 
00133             i.e. map[workflow_id]=(workflow definition map, )
00134             each workflow defintion map contains the following:
00135             (worklist)id, guard (Guard instance), guard_permissions (permission of Guard instance),
00136             guard_roles (roles of Guard instance), catalog_vars (mapping), actbox_name (actions box label),
00137             actbox_url (actions box url) and types (list of portal types)
00138         """
00139         # We want to know which types use the workflows with worklists
00140         # This for example avoids displaying 'pending' of multiple workflows in the same worklist
00141         types_tool = getToolByName(self, 'portal_types')
00142         list_ptypes = types_tool.listContentTypes()
00143         types_by_wf = {} # wf:[list,of,types]
00144         for t in list_ptypes:
00145             for wf in self.getChainFor(t):
00146                 types_by_wf[wf] = types_by_wf.get(wf,[]) + [t]
00147 
00148         # Placeful stuff
00149         placeful_tool = getToolByName(self, 'portal_placeful_workflow', None)
00150         if placeful_tool is not None:
00151             for policy in placeful_tool.getWorkflowPolicies():
00152                 for t in list_ptypes:
00153                     chain = policy.getChainFor(t) or ()
00154                     for wf in chain:
00155                         types_by_wf[wf] = types_by_wf.get(wf,[]) + [t]
00156 
00157         wf_with_wlists = {}
00158         for id in self.getWorkflowIds():
00159             # the above list incomprehension merely _flattens_ nested sequences into 1 sequence
00160 
00161             wf=self.getWorkflowById(id)
00162             if hasattr(wf, 'worklists'):
00163                 wlists = []
00164                 for worklist in wf.worklists._objects:
00165                     wlist_def=wf.worklists._mapping[worklist['id']]
00166                     # Make the var_matches a dict instead of PersistentMapping to enable access from scripts
00167                     var_matches = {}
00168                     for key in wlist_def.var_matches.keys(): var_matches[key] = wlist_def.var_matches[key]
00169                     a_wlist = { 'id':worklist['id']
00170                               , 'guard' : wlist_def.getGuard()
00171                               , 'guard_permissions' : wlist_def.getGuard().permissions
00172                               , 'guard_roles' : wlist_def.getGuard().roles
00173                               , 'catalog_vars' : var_matches
00174                               , 'name' : getattr(wlist_def, 'actbox_name', None)
00175                               , 'url' : getattr(wlist_def, 'actbox_url', None)
00176                               , 'types' : types_by_wf.get(id,[]) }
00177                     wlists.append(a_wlist)
00178                 # yes, we can duplicates, we filter duplicates out on the calling PyhtonScript client
00179                 wf_with_wlists[id]=wlists
00180 
00181         return wf_with_wlists
00182 
00183     security.declarePublic('getWorklistsResults')
00184     def getWorklistsResults(self):
00185         """Return all the objects concerned by one or more worklists
00186 
00187         This method replace 'getWorklists' by implementing the whole worklists
00188         work for the script.
00189         An object is returned only once, even if is return by several worklists.
00190         Make the whole work as expensive it is.
00191         """
00192         sm = getSecurityManager()
00193         # We want to know which types use the workflows with worklists
00194         # This for example avoids displaying 'pending' of multiple workflows in the same worklist
00195         types_tool = getToolByName(self, 'portal_types')
00196         catalog = getToolByName(self, 'portal_catalog')
00197 
00198         list_ptypes = types_tool.listContentTypes()
00199         types_by_wf = {} # wf:[list,of,types]
00200         for t in list_ptypes:
00201             for wf in self.getChainFor(t):
00202                 types_by_wf[wf] = types_by_wf.get(wf, []) + [t]
00203 
00204         # PlacefulWorkflowTool will give us other results
00205         placeful_tool = getToolByName(self, 'portal_placeful_workflow', None)
00206         if placeful_tool is not None:
00207             for policy in placeful_tool.getWorkflowPolicies():
00208                 for t in list_ptypes:
00209                     chain = policy.getChainFor(t) or ()
00210                     for wf in chain:
00211                         types_by_wf[wf] = types_by_wf.get(wf, []) + [t]
00212 
00213         objects_by_path = {}
00214         for id in self.getWorkflowIds():
00215 
00216             wf=self.getWorkflowById(id)
00217             if hasattr(wf, 'worklists'):
00218                 wlists = []
00219                 for worklist in wf.worklists._objects:
00220                     wlist_def=wf.worklists._mapping[worklist['id']]
00221                     # Make the var_matches a dict instead of PersistentMapping to enable access from scripts
00222                     catalog_vars = dict(portal_type=types_by_wf.get(id, []))
00223                     for key in wlist_def.var_matches.keys():
00224                         catalog_vars[key] = wlist_def.var_matches[key]
00225                     for result in catalog.searchResults(**catalog_vars):
00226                         o = result.getObject()
00227                         if o \
00228                            and id in self.getChainFor(o) \
00229                            and wlist_def.getGuard().check(sm, wf, o):
00230                             absurl = o.absolute_url()
00231                             if absurl:
00232                                 objects_by_path[absurl] = (o.modified(), o)
00233 
00234         results = objects_by_path.values()
00235         results.sort()
00236         return tuple([ obj[1] for obj in results ])
00237 
00238 
00239     security.declareProtected(ManagePortal, 'getChainForPortalType')
00240     def getChainForPortalType(self, pt_name, managescreen=0):
00241         """ Get a chain for a specific portal type.
00242         """
00243         if self._chains_by_type.has_key(pt_name):
00244             return self._chains_by_type[pt_name]
00245         else:
00246             # (Default) is _not_ a chain nor a workflow in a chain.
00247             if managescreen:
00248                 return '(Default)'
00249             else:
00250                 # Return the default chain.
00251                 return self._default_chain
00252 
00253     security.declareProtected(ManagePortal, 'listWorkflows')
00254     def listWorkflows(self):
00255         """ Return the list of workflows
00256         """
00257         return self.objectIds()
00258 
00259     security.declarePublic('getTitleForStateOnType')
00260     def getTitleForStateOnType(self, state_name, p_type):
00261         """Returns the workflow state title for a given state name,
00262            uses a portal_type to determine which workflow to use
00263         """
00264         if state_name and p_type is not None:
00265             chain = self.getChainForPortalType(p_type)
00266             for wf_id in chain:
00267                 wf = self.getWorkflowById(wf_id)
00268                 if wf is not None:
00269                     states = wf.states
00270                     state = getattr(states, state_name, None)
00271                     if state is not None:
00272                         return getattr(aq_base(state), 'title', None) or state_name
00273         return state_name
00274 
00275     security.declarePublic('getTitleForTransitionOnType')
00276     def getTitleForTransitionOnType(self, trans_name, p_type):
00277         """Returns the workflow transition title for a given transition name,
00278            uses a portal_type to determine which workflow to use
00279         """
00280         if trans_name and p_type is not None:
00281             chain = self.getChainForPortalType(p_type)
00282             for wf_id in chain:
00283                 wf = self.getWorkflowById(wf_id)
00284                 if wf is not None:
00285                     transitions = wf.transitions
00286                     trans = getattr(transitions, trans_name, None)
00287                     if trans is not None:
00288                         return getattr(aq_base(trans), 'actbox_name', None) or trans_name
00289         return trans_name
00290 
00291     security.declarePublic('listWFStatesByTitle')
00292     def listWFStatesByTitle(self, filter_similar=False):
00293         """Returns the states of all available workflows, optionally filtering
00294            out states with matching title and id"""
00295         states = []
00296         dup_list = {}
00297         for wf in self.objectValues():
00298             state_folder = getattr(wf, 'states', None)
00299             if state_folder is not None:
00300                 if not filter_similar:
00301                     states.extend(state_folder.objectValues())
00302                 else:
00303                     for state in state_folder.objectValues():
00304                         key = '%s:%s'%(state.id,state.title)
00305                         if not dup_list.has_key(key):
00306                             states.append(state)
00307                         dup_list[key] = 1
00308         return [(s.title, s.getId()) for s in states]
00309 
00310     # PLIP 217 Workflow by adaptation
00311     def getChainFor( self, ob ):
00312         """
00313         Returns the chain that applies to the given object.
00314         If we get a string as the ob parameter, use it as
00315         the portal_type.
00316         """
00317         return getMultiAdapter( (ob, self), IWorkflowChain )
00318 
00319 WorkflowTool.__doc__ = BaseTool.__doc__
00320 
00321 InitializeClass(WorkflowTool)