diff --git a/product/ERP5/Interaction.py b/product/ERP5/Interaction.py index 81433a3c3f74ff69729d2cfabc0e60c473c488b7..0115f33f5e81e74b1c976536ff5af7c91f65ee06 100755 --- a/product/ERP5/Interaction.py +++ b/product/ERP5/Interaction.py @@ -34,14 +34,10 @@ from Products.CMFCore.CMFCorePermissions import ManagePortal from Products.DCWorkflow.ContainerTab import ContainerTab from Products.DCWorkflow.Guard import Guard from Products.DCWorkflow.Expression import Expression +from Products.DCWorkflow.Transitions import TRIGGER_AUTOMATIC, TRIGGER_WORKFLOW_METHOD from Products.ERP5 import _dtmldir -TRIGGER_AUTOMATIC = 0 -TRIGGER_USER_ACTION = 1 # useless for interaction -TRIGGER_WORKFLOW_METHOD = 2 - - class InteractionDefinition (SimpleItem): meta_type = 'Workflow Interaction' @@ -51,14 +47,16 @@ class InteractionDefinition (SimpleItem): title = '' description = '' new_state_id = '' - trigger_type = TRIGGER_USER_ACTION + trigger_type = TRIGGER_WORKFLOW_METHOD guard = None actbox_name = '' actbox_url = '' actbox_category = 'workflow' var_exprs = None # A mapping. - script_name = None # Executed before transition - after_script_name = None # Executed after transition + script_name = () # Executed before transition + after_script_name = () # Executed after transition + activate_script_name = () # Executed as activity + method_id = None manage_options = ( {'label': 'Properties', 'action': 'manage_properties'}, @@ -115,20 +113,29 @@ class InteractionDefinition (SimpleItem): manage_tabs_message=manage_tabs_message, ) - def setProperties(self, title, new_state_id, - trigger_type=TRIGGER_USER_ACTION, script_name='', - after_script_name='', + def setProperties(self, title, + portal_type_filter=None, + trigger_type=TRIGGER_AUTOMATIC, + script_name=(), + after_script_name=(), + activate_script_name=(), actbox_name='', actbox_url='', actbox_category='workflow', + method_id=None, props=None, REQUEST=None, description=''): ''' + Update transition properties + XXX - then make sure that method_id is WorkflowMethod for portal_type_filter + XXX - this will likely require dynamic ''' + self.method_id = method_id + self.portal_type_filter = portal_type_filter self.title = str(title) self.description = str(description) - self.new_state_id = str(new_state_id) self.trigger_type = int(trigger_type) - self.script_name = str(script_name) - self.after_script_name = str(after_script_name) + self.script_name = map(lambda x: str(x), script_name) + self.after_script_name = map(lambda x: str(x), after_script_name) + self.activate_script_name = map(lambda x: str(x), activate_script_name) g = Guard() if g.changeFromProperties(props or REQUEST): self.guard = g diff --git a/product/ERP5/InteractionWorkflow.py b/product/ERP5/InteractionWorkflow.py index f421fb3a1c4e93209e9ef3a63d63e6c6831d9a83..f5f3a4c747bf645246f4fc75b5fe8f11bba178f0 100755 --- a/product/ERP5/InteractionWorkflow.py +++ b/product/ERP5/InteractionWorkflow.py @@ -18,16 +18,21 @@ import Globals import App -from AccessControl import ClassSecurityInfo +from AccessControl import getSecurityManager, ClassSecurityInfo from Products.CMFCore.utils import getToolByName, _getAuthenticatedUser from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition +from Products.DCWorkflow.Transitions import TRIGGER_AUTOMATIC, TRIGGER_WORKFLOW_METHOD +from Products.CMFCore.WorkflowCore import WorkflowException, \ + ObjectDeleted, ObjectMoved +from Products.DCWorkflow.Expression import StateChangeInfo, createExprContext from Products.CMFCore.WorkflowTool import addWorkflowFactory +from Products.CMFActivity.ActiveObject import ActiveObject from zLOG import LOG -class InteractionWorkflowDefinition (DCWorkflowDefinition): +class InteractionWorkflowDefinition (DCWorkflowDefinition, ActiveObject): """ The InteractionTool implements portal object interaction policies. @@ -95,7 +100,6 @@ class InteractionWorkflowDefinition (DCWorkflowDefinition): {'label': 'Interactions', 'action': 'interactions/manage_main'}, {'label': 'Variables', 'action': 'variables/manage_main'}, {'label': 'Scripts', 'action': 'scripts/manage_main'}, - {'label': 'Permissions', 'action': 'manage_permissions'}, ) + App.Undo.UndoSupport.manage_options def __init__(self, id): @@ -109,6 +113,313 @@ class InteractionWorkflowDefinition (DCWorkflowDefinition): from Products.DCWorkflow.Scripts import Scripts self._addObject(Scripts('scripts')) + def _executeTransition(self, ob, tdef=None, kwargs=None): + ''' + Private method. + Puts object in a new state. + ''' + sci = None + econtext = None + moved_exc = None + + # Figure out the old and new states. + old_sdef = self._getWorkflowStateOf(ob) + old_state = old_sdef.getId() + if tdef is None: + new_state = self.initial_state + former_status = {} + else: + new_state = tdef.new_state_id + if not new_state: + # Stay in same state. + new_state = old_state + former_status = self._getStatusOf(ob) + new_sdef = self.states.get(new_state, None) + if new_sdef is None: + raise WorkflowException, ( + 'Destination state undefined: ' + new_state) + + # Execute the "before" script. + if tdef is not None: + for script_name in tdef.script_name: + script = self.scripts[script_name] + # Pass lots of info to the script in a single parameter. + sci = StateChangeInfo( + ob, self, former_status, tdef, old_sdef, new_sdef, kwargs) + try: + script(sci) # May throw an exception. + except ObjectMoved, moved_exc: + ob = moved_exc.getNewObject() + # Re-raise after transition + + # Update variables. + state_values = new_sdef.var_values + if state_values is None: state_values = {} + tdef_exprs = None + if tdef is not None: tdef_exprs = tdef.var_exprs + if tdef_exprs is None: tdef_exprs = {} + status = {} + for id, vdef in self.variables.items(): + if not vdef.for_status: + continue + expr = None + if state_values.has_key(id): + value = state_values[id] + elif tdef_exprs.has_key(id): + expr = tdef_exprs[id] + elif not vdef.update_always and former_status.has_key(id): + # Preserve former value + value = former_status[id] + else: + if vdef.default_expr is not None: + expr = vdef.default_expr + else: + value = vdef.default_value + if expr is not None: + # Evaluate an expression. + if econtext is None: + # Lazily create the expression context. + if sci is None: + sci = StateChangeInfo( + ob, self, former_status, tdef, + old_sdef, new_sdef, kwargs) + econtext = createExprContext(sci) + value = expr(econtext) + status[id] = value + + # Update state. + status[self.state_var] = new_state + tool = aq_parent(aq_inner(self)) + tool.setStatusOf(self.id, ob, status) + + # Update role to permission assignments. + self.updateRoleMappingsFor(ob) + + # Execute the "after" script. + if tdef is not None: + for after_script_name in tdef.after_script_name: + script = self.scripts[after_script_name] + # Pass lots of info to the script in a single parameter. + sci = StateChangeInfo( + ob, self, status, tdef, old_sdef, new_sdef, kwargs) + script(sci) # May throw an exception. + + # Execute the "activate" script. + if tdef is not None: + for after_script_name in tdef.activate_script_name: + script = self.scripts[after_script_name] + # Pass lots of info to the script in a single parameter. + sci = StateChangeInfo( + ob.activate(activity='SQLQueue'), None, status, tdef, old_sdef, new_sdef, kwargs) + script(sci) # May throw an exception. + + # Return the new state object. + if moved_exc is not None: + # Propagate the notification that the object has moved. + raise moved_exc + else: + return new_sdef + + security.declarePrivate('listObjectActions') + def listObjectActions(self, info): + return [] + + security.declarePrivate('isInfoSupported') + def isInfoSupported(self, ob, name): + ''' + Returns a true value if the given info name is supported. + ''' + vdef = self.variables.get(name, None) + if vdef is None: + return 0 + return 1 + + security.declarePrivate('getInfoFor') + def getInfoFor(self, ob, name, default): + ''' + Allows the user to request information provided by the + workflow. This method must perform its own security checks. + ''' + vdef = self.variables[name] + if vdef.info_guard is not None and not vdef.info_guard.check( + getSecurityManager(), self, ob): + return default + status = self._getStatusOf(ob) + if status is not None and status.has_key(name): + value = status[name] + # Not set yet. Use a default. + elif vdef.default_expr is not None: + ec = createExprContext(StateChangeInfo(ob, self, status)) + value = vdef.default_expr(ec) + else: + value = vdef.default_value + + return value + + security.declarePrivate('isWorkflowMethodSupported') + def isWorkflowMethodSupported(self, ob, method_id): + ''' + Returns a true value if the given workflow is + automatic with the propper method_id + ''' + return 0 + for t in self.interactions.values(): + if t.trigger_type == TRIGGER_WORKFLOW_METHOD: + if t.method_id == method_id: + if t.portal_type_filter is None: + return 1 + elif ob.getPortalType() in t.portal_type_filter: + return 1 + return 0 + + + security.declarePrivate('wrapWorkflowMethod') + def wrapWorkflowMethod(self, ob, method_id, func, args, kw): + ''' + Allows the user to request a workflow action. This method + must perform its own security checks. + ''' + + for t in self.interactions.values(): + tdef = None + if t.trigger_type == TRIGGER_WORKFLOW_METHOD: + if t.method_id == method_id: + if t.portal_type_filter is None: + tdef = t + elif ob.getPortalType() in t.portal_type_filter: + tdef = t + if tdef is not None: + # Only execute if guard OK + if self._checkTransitionGuard(tdef, ob): + res = apply(func, args, kw) + try: + self._changeStateOf(ob, tdef) + except ObjectDeleted: + # Re-raise with a different result. + raise ObjectDeleted(res) + except ObjectMoved, ex: + # Re-raise with a different result. + raise ObjectMoved(ex.getNewObject(), res) + return res + + security.declarePrivate('notifyBefore') + def notifyBefore(self, ob, action, args=None, kw=None): + ''' + Notifies this workflow of an action before it happens, + allowing veto by exception. Unless an exception is thrown, either + a notifySuccess() or notifyException() can be expected later on. + The action usually corresponds to a method name. + ''' + for t in self.interactions.values(): + tdef = None + if t.trigger_type == TRIGGER_AUTOMATIC: + if t.portal_type_filter is None: + tdef = t + elif ob.getPortalType() in t.portal_type_filter: + tdef = t + elif t.trigger_type == TRIGGER_WORKFLOW_METHOD: + if t.method_id == action: + if t.portal_type_filter is None: + tdef = t + elif ob.getPortalType() in t.portal_type_filter: + tdef = t + if tdef is not None: + former_status = self._getStatusOf(ob) + # Execute the "before" script. + for script_name in tdef.script_name: + script = self.scripts[script_name] + # Pass lots of info to the script in a single parameter. + sci = StateChangeInfo( + ob, self, former_status, tdef, None, None, None) + try: + script(sci) # May throw an exception. + except ObjectMoved, moved_exc: + ob = moved_exc.getNewObject() + # Re-raise after transition + return + + security.declarePrivate('notifySuccess') + def notifySuccess(self, ob, action, result, args=None, kw=None): + ''' + Notifies this workflow that an action has taken place. + ''' + for t in self.interactions.values(): + tdef = None + if t.trigger_type == TRIGGER_AUTOMATIC: + if t.portal_type_filter is None: + tdef = t + elif ob.getPortalType() in t.portal_type_filter: + tdef = t + elif t.trigger_type == TRIGGER_WORKFLOW_METHOD: + if t.method_id == action: + if t.portal_type_filter is None: + tdef = t + elif ob.getPortalType() in t.portal_type_filter: + tdef = t + if tdef is not None: + # Update variables. + former_status = self._getStatusOf(ob) + tdef_exprs = tdef.var_exprs + if tdef_exprs is None: tdef_exprs = {} + status = {} + for id, vdef in self.variables.items(): + if not vdef.for_status: + continue + expr = None + if tdef_exprs.has_key(id): + expr = tdef_exprs[id] + elif not vdef.update_always and former_status.has_key(id): + # Preserve former value + value = former_status[id] + else: + if vdef.default_expr is not None: + expr = vdef.default_expr + else: + value = vdef.default_value + if expr is not None: + # Evaluate an expression. + if econtext is None: + # Lazily create the expression context. + if sci is None: + sci = StateChangeInfo( + ob, self, former_status, tdef, + None, None, None) + econtext = createExprContext(sci) + value = expr(econtext) + status[id] = value + + # Update state. + tool = aq_parent(aq_inner(self)) + tool.setStatusOf(self.id, ob, status) + + # Execute the "after" script. + for script_name in tdef.after_script_name: + script = self.scripts[script_name] + # Pass lots of info to the script in a single parameter. + sci = StateChangeInfo( + ob, self, status, tdef, None, None, None) + try: + script(sci) # May throw an exception. + except ObjectMoved, moved_exc: + ob = moved_exc.getNewObject() + # Re-raise after transition + + # Execute the "after" script. + for script_name in tdef.activate_script_name: + self.activate(activity='SQLQueue').activeScript(script_name, ob.getRelativeUrl(), status, tdef.id) + + return + + security.declarePrivate('activeScript') + def activeScript(self, script_name, ob_url, status, tdef_id): + script = self.scripts[script_name] + ob = self.restrictedTraverse(ob_url) + tdef = self.interactions.get(tdef_id) + sci = StateChangeInfo( + ob, self, status, tdef, None, None, None) + script(sci) + + Globals.InitializeClass(InteractionWorkflowDefinition) addWorkflowFactory(InteractionWorkflowDefinition, id='interaction_workflow',