[CMF-checkins] CVS: CMF/DCWorkflow - DCWorkflow.py:1.6 Default.py:1.3 Expression.py:1.5 Guard.py:1.3 States.py:1.3 Transitions.py:1.2 Variables.py:1.2

Shane Hathaway shane@digicool.com
Fri, 31 Aug 2001 10:37:13 -0400


Update of /cvs-repository/CMF/DCWorkflow
In directory cvs.zope.org:/tmp/cvs-serv7877

Modified Files:
	DCWorkflow.py Default.py Expression.py Guard.py States.py 
	Transitions.py Variables.py 
Log Message:
- Thanks to Ulrich Eck (ueck@net-labs.de), you can now set variables
  on states and transitions.  Great job!

- Changed expressions to TALES.

- Added a second default workflow consisting of the "classic" default.
  


=== CMF/DCWorkflow/DCWorkflow.py 1.5 => 1.6 ===
 from Transitions import TRIGGER_AUTOMATIC, TRIGGER_USER_ACTION, \
      TRIGGER_WORKFLOW_METHOD
-from Expression import exprNamespace
+from Expression import StateChangeInfo, createExprContext
 
 Unauthorized = 'Unauthorized'
 
@@ -126,18 +126,6 @@
         raise ValueError, 'Illegal ID'
     return 1
 
-class NamespaceAccessor:
-    __allow_access_to_unprotected_subobjects__ = 1
-    def __init__(self, md):
-        self._getitem = md.getitem
-    def __getattr__(self, name):
-        try:
-            return self._getitem(name, 0)
-        except KeyError:
-            raise AttributeError, name
-    def __getitem__(self, name):
-        return self._getitem(name, 0)
-
 
 class DCWorkflowDefinition (WorkflowUIMixin, Folder):
     '''
@@ -154,7 +142,7 @@
 
     states = None
     transitions = None
-    vars = None
+    variables = None
     worklists = None
     scripts = None
 
@@ -239,8 +227,8 @@
 
                 # Not set yet.  Use a default.
                 elif vdef.default_expr is not None:
-                    md = exprNamespace(ob, self, status)
-                    value = vdef.default_expr(md)
+                    ec = createExprContext(StateChangeInfo(ob, self, status))
+                    value = vdef.default_expr(ec)
                 else:
                     value = vdef.default_value
 
@@ -431,8 +419,8 @@
 
         # Not set yet.  Use a default.
         elif vdef.default_expr is not None:
-            md = exprNamespace(ob, self, status)
-            value = vdef.default_expr(md)
+            ec = createExprContext(StateChangeInfo(ob, self, status))
+            value = vdef.default_expr(ec)
         else:
             value = vdef.default_value
 
@@ -535,35 +523,42 @@
         Private method.
         Puts object in a new state.
         '''
+        sci = None
+        econtext = None
         moved = 0
+
+        # Figure out the old and new states.
+        old_sdef = self._getWorkflowStateOf(ob)
+        old_state = old_sdef.getId()
         if tdef is None:
-            state = self.initial_state
+            new_state = self.initial_state
             former_status = {}
-            action = ''
         else:
-            state = tdef.new_state_id
-            if not state:
+            new_state = tdef.new_state_id
+            if not new_state:
                 # Stay in same state.
-                state = self._getWorkflowStateOf(ob, 1)
+                new_state = old_state
             former_status = self._getStatusOf(ob)
-            action = tdef.id
-            # Execute a script if specified.
-            if tdef.script_name:
-                script = self.scripts[tdef.script_name]
-                # Pass lots of info to the script in a single parameter.
-                md = exprNamespace(ob, self, former_status,
-                                   action, state, kwargs)
-                accessor = NamespaceAccessor(md)
-                try:
-                    script(accessor)  # May throw an exception.
-                except ObjectMoved, ex:
-                    ob = ex.getNewObject()
-                    moved = 1
-        sdef = self.states.get(state, None)
-        if sdef is None:
-            raise WorkflowException, 'Destination state undefined: ' + state
+        new_sdef = self.states.get(new_state, None)
+        if new_sdef is None:
+            raise WorkflowException, (
+                'Destination state undefined: ' + new_state)
+
+        # Execute a script if specified.
+        if tdef is not None and tdef.script_name:
+            script = self.scripts[tdef.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, ex:
+                ob = ex.getNewObject()
+                moved = 1
+                # Don't re-raise
+
         # Update variables.
-        state_values = sdef.var_values
+        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
@@ -572,31 +567,41 @@
         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):
-                md = exprNamespace(ob, self, former_status,
-                                   action, state, kwargs)
-                value = tdef_exprs[id](md)
+                expr = tdef_exprs[id]
             elif vdef.default_expr is not None:
-                md = exprNamespace(ob, self, former_status,
-                                   action, state, kwargs)
-                value = vdef.default_expr(md)
+                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] = 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)
 
+        # Return the new state object.
         if moved:
             # Re-raise.
             raise ObjectMoved(ob)
         else:
-            return sdef
+            return new_sdef
 
 
 Globals.InitializeClass(DCWorkflowDefinition)


=== CMF/DCWorkflow/Default.py 1.2 => 1.3 ===
 
 
-def setupDefaultWorkflow(wf):
+
+def setupDefaultWorkflowRev2(wf):
     '''
+    Sets up a DCWorkflow with the addition of a visible state,
+    the show and hide transitions, and corresponding changes.
     wf is a DCWorkflow instance.
     '''
-    wf.setProperties(title='CMF default workflow rev 2')
+    wf.setProperties(title='CMF default workflow [Revision 2]')
 
     for s in ('private', 'visible', 'pending', 'published'):
         wf.states.addState(s)
@@ -208,29 +211,161 @@
 
     vdef = wf.variables['action']
     vdef.setProperties(description='The last transition',
-                       default_expr='transition',
+                       default_expr='transition/getId',
+                       for_status=1)
+
+    vdef = wf.variables['actor']
+    vdef.setProperties(description='The ID of the user who performed '
+                       'the last transition',
+                       default_expr='user/getUserName',
+                       for_status=1)
+
+    vdef = wf.variables['comments']
+    vdef.setProperties(description='Comments about the last transition',
+                       default_expr="python:state_change.kwargs.get('comment', '')",
+                       for_status=1)
+
+    vdef = wf.variables['review_history']
+    vdef.setProperties(description='Provides access to workflow history',
+                       default_expr="state_change/getHistory",
+                       props={'guard_permissions':
+                              p_request + ';' + p_review})
+
+    vdef = wf.variables['time']
+    vdef.setProperties(description='Time of the last transition',
+                       default_expr="state_change/getDateTime",
+                       for_status=1)
+
+    ldef = wf.worklists['reviewer_queue']
+    ldef.setProperties(description='Reviewer tasks',
+                       actbox_name='Pending (%(count)d)',
+                       actbox_url='%(portal_url)s/search?review_state=pending',
+                       props={'var_match_review_state':'pending',
+                              'guard_permissions':p_review})
+    
+
+def createDefaultWorkflowRev2(id):
+    '''
+    '''
+    ob = DCWorkflowDefinition(id)
+    setupDefaultWorkflowRev2(ob)
+    return ob
+
+addWorkflowFactory(createDefaultWorkflowRev2, id='default_workflow',
+                   title='Web-configurable workflow [Revision 2]')
+
+
+
+
+
+
+
+
+
+def setupDefaultWorkflowClassic(wf):
+    '''
+    Sets up a DCWorkflow as close as possible to the old DefaultWorkflow,
+    with only the private, pending, and published states.
+    wf is a DCWorkflow instance.
+    '''
+    wf.setProperties(title='CMF default workflow [Classic]')
+
+    for s in ('private', 'pending', 'published'):
+        wf.states.addState(s)
+    for t in ('publish', 'reject', 'retract', 'submit'):
+        wf.transitions.addTransition(t)
+    for v in ('action', 'actor', 'comments', 'review_history', 'time'):
+        wf.variables.addVariable(v)
+    for l in ('reviewer_queue',):
+        wf.worklists.addWorklist(l)
+    for p in (p_access, p_modify, p_view):
+        wf.addManagedPermission(p)
+
+    wf.states.setInitialState('private')
+
+    sdef = wf.states['private']
+    sdef.setProperties(
+        title='Non-visible and editable only by owner',
+        transitions=('submit', 'publish',))
+    sdef.setPermission(p_access, 0, (r_manager, r_owner))
+    sdef.setPermission(p_view, 0, (r_manager, r_owner))
+    sdef.setPermission(p_modify, 0, (r_manager, r_owner))
+
+    sdef = wf.states['pending']
+    sdef.setProperties(
+        title='Waiting for reviewer',
+        transitions=('publish', 'reject', 'retract',))
+    sdef.setPermission(p_access, 1, (r_anon, r_manager, r_reviewer))
+    sdef.setPermission(p_view, 1, (r_anon, r_manager, r_reviewer))
+    sdef.setPermission(p_modify, 0, (r_manager, r_reviewer))
+
+    sdef = wf.states['published']
+    sdef.setProperties(
+        title='Public',
+        transitions=('reject', 'retract',))
+    sdef.setPermission(p_access, 1, (r_anon, r_manager))
+    sdef.setPermission(p_view, 1, (r_anon, r_manager))
+    sdef.setPermission(p_modify, 0, (r_manager))
+
+    tdef = wf.transitions['publish']
+    tdef.setProperties(
+        title='Reviewer publishes content',
+        new_state_id='published',
+        actbox_name='Publish',
+        actbox_url='%(content_url)s/content_publish_form',
+        props={'guard_permissions':p_review})
+
+    tdef = wf.transitions['reject']
+    tdef.setProperties(
+        title='Reviewer rejects submission',
+        new_state_id='private',
+        actbox_name='Reject',
+        actbox_url='%(content_url)s/content_reject_form',
+        props={'guard_permissions':p_review})
+
+    tdef = wf.transitions['retract']
+    tdef.setProperties(
+        title='Member retracts submission',
+        new_state_id='private',
+        actbox_name='Retract',
+        actbox_url='%(content_url)s/content_retract_form',
+        props={'guard_permissions':p_request})
+
+    tdef = wf.transitions['submit']
+    tdef.setProperties(
+        title='Member requests publishing',
+        new_state_id='pending',
+        actbox_name='Submit',
+        actbox_url='%(content_url)s/content_submit_form',
+        props={'guard_permissions':p_request})
+
+    wf.variables.setStateVar('review_state')
+
+    vdef = wf.variables['action']
+    vdef.setProperties(description='The last transition',
+                       default_expr='transition/getId',
                        for_status=1)
 
     vdef = wf.variables['actor']
     vdef.setProperties(description='The ID of the user who performed '
                        'the last transition',
-                       default_expr='_.SecurityGetUser().getUserName()',
+                       default_expr='user/getUserName',
                        for_status=1)
 
     vdef = wf.variables['comments']
     vdef.setProperties(description='Comments about the last transition',
-                       default_expr="kwargs.get('comment', '')",
+                       default_expr="python:state_change.kwargs.get('comment', '')",
                        for_status=1)
 
     vdef = wf.variables['review_history']
     vdef.setProperties(description='Provides access to workflow history',
-                       default_expr="getHistory()",
+                       default_expr="state_change/getHistory",
                        props={'guard_permissions':
                               p_request + ';' + p_review})
 
     vdef = wf.variables['time']
     vdef.setProperties(description='Time of the last transition',
-                       default_expr="_.DateTime()",
+                       default_expr="state_change/getDateTime",
                        for_status=1)
 
     ldef = wf.worklists['reviewer_queue']
@@ -241,12 +376,14 @@
                               'guard_permissions':p_review})
     
 
-def createDefaultWorkflow(id):
+
+def createDefaultWorkflowClassic(id):
     '''
     '''
     ob = DCWorkflowDefinition(id)
-    setupDefaultWorkflow(ob)
+    setupDefaultWorkflowClassic(ob)
     return ob
 
-addWorkflowFactory(createDefaultWorkflow, id='default_workflow',
-                   title='Web-configurable workflow [default]')
+addWorkflowFactory(createDefaultWorkflowClassic, id='default_workflow',
+                   title='Web-configurable workflow [Classic]')
+


=== CMF/DCWorkflow/Expression.py 1.4 => 1.5 ===
 from Acquisition import aq_inner, aq_parent
 from AccessControl import getSecurityManager, ClassSecurityInfo
-from DocumentTemplate.DT_Util import TemplateDict, InstanceDict, Eval
+from DateTime import DateTime
 
 from Products.CMFCore.WorkflowCore import ObjectDeleted, ObjectMoved
+from Products.PageTemplates.Expressions import getEngine
+from Products.PageTemplates.TALES import SafeMapping
+from Products.PageTemplates.PageTemplate import ModuleImporter
 
-try:
-    # Zope 2.3.x
-    from DocumentTemplate.DT_Util import expr_globals
-except ImportError:
-    expr_globals = None
-
-try:
-    # Zope 2.4.x
-    from AccessControl.DTML import RestrictedDTML
-except ImportError:
-    class RestrictedDTML: pass
 
-
-class Expression (Persistent, RestrictedDTML):
+class Expression (Persistent):
     text = ''
-    _v_eval = None
+    _v_compiled = None
 
     security = ClassSecurityInfo()
 
     def __init__(self, text):
         self.text = text
-        if expr_globals is not None:
-            eval = Eval(text, expr_globals)
-        else:
-            eval = Eval(text)
-        self._v_eval = eval
+        self._v_compiled = getEngine().compile(text)
 
-    security.declarePrivate('validate')
-    def validate(self, inst, parent, name, value, md):
-        # Zope 2.3.x
-        return getSecurityManager().validate(inst, parent, name, value)
-
-    def __call__(self, md):
-        # md is a TemplateDict instance.
-        eval = self._v_eval
-        if eval is None:
-            text = self.text
-            if expr_globals is not None:
-                eval = Eval(text, expr_globals)
-            else:
-                eval = Eval(text)
-            self._v_eval = eval
-        md.validate = self.validate  # Zope 2.3.x
-        # Zope 2.4.x
-        md.guarded_getattr = getattr(self, 'guarded_getattr', None)
-        md.guarded_getitem = getattr(self, 'guarded_getitem', None)
-        return eval.eval(md)
+    def __call__(self, econtext):
+        compiled = self._v_compiled
+        if compiled is None:
+            compiled = self._v_compiled = getEngine().compile(self.text)
+        # ?? Maybe expressions should manipulate the security
+        # context stack.
+        res = compiled(econtext)
+        if isinstance(res, Exception):
+            raise res
+        #print 'returning %s from %s' % (`res`, self.text)
+        return res
 
 Globals.InitializeClass(Expression)
 
 
-def exprNamespace(object, workflow, status=None,
-                  transition=None, new_state=None, kwargs=None):
-    md = TemplateDict()
-    if kwargs is None:
-        kwargs = {}
-    if status is None:
-        tool = aq_parent(aq_inner(workflow))
-        status = tool.getStatusOf(workflow.id, object)
-        if status is None:
-            status = {}
-    md._push(status)
-    md._push(ExprVars(object, workflow))
-    d = {'object': object,
-         'workflow': workflow,
-         'transition': transition,
-         'new_state': new_state,
-         'kwargs': kwargs,
-         }
-    md._push(d)
-    md._push(workflow.scripts)  # Make scripts automatically available.
-    return md
-
-
-class ExprVars:
+class StateChangeInfo:
     '''
-    Provides names that are more expensive to compute.
+    Provides information for expressions and scripts.
     '''
+    _date = None
+
     ObjectDeleted = ObjectDeleted
     ObjectMoved = ObjectMoved
 
-    def __init__(self, ob, wf):
-        self._ob = ob
-        self._wf = wf
+    security = ClassSecurityInfo()
+    security.setDefaultAccess('allow')
+
+    def __init__(self, object, workflow, status=None, transition=None,
+                 old_state=None, new_state=None, kwargs=None):
+        if kwargs is None:
+            kwargs = {}
+        else:
+            # Don't allow mutation
+            kwargs = SafeMapping(kwargs)
+        if status is None:
+            tool = aq_parent(aq_inner(workflow))
+            status = tool.getStatusOf(workflow.id, object)
+            if status is None:
+                status = {}
+        if status:
+            # Don't allow mutation
+            status = SafeMapping(status)
+        self.object = object
+        self.workflow = workflow
+        self.old_state = old_state
+        self.new_state = new_state
+        self.transition = transition
+        self.status = status
+        self.kwargs = kwargs
 
     def __getitem__(self, name):
         if name[:1] != '_' and hasattr(self, name):
@@ -187,20 +166,49 @@
         raise KeyError, name
 
     def getHistory(self):
-        wf = self._wf
+        wf = self.workflow
         tool = aq_parent(aq_inner(wf))
         wf_id = wf.id
-        h = tool.getHistoryOf(wf_id, self._ob)
+        h = tool.getHistoryOf(wf_id, self.object)
         if h:
             return map(lambda dict: dict.copy(), h)  # Don't allow mutation
         else:
             return ()
 
     def getPortal(self):
-        ob = self._ob
+        ob = self.object
         while ob is not None and not getattr(ob, '_isPortalRoot', 0):
             ob = aq_parent(aq_inner(ob))
         return ob
 
-    def getObjectContainer(self):
-        return aq_parent(aq_inner(self._ob))
+    def getDateTime(self):
+        date = self._date
+        if not date:
+            date = self._date = DateTime()
+        return date
+
+Globals.InitializeClass(StateChangeInfo)
+
+
+def createExprContext(sci):
+    '''
+    An expression context provides names for TALES expressions.
+    '''
+    ob = sci.object
+    wf = sci.workflow
+    data = {
+        'here':         ob,
+        'container':    aq_parent(aq_inner(ob)),
+        'nothing':      None,
+        'root':         wf.getPhysicalRoot(),
+        'request':      ob.REQUEST,
+        'modules':      ModuleImporter,
+        'user':         getSecurityManager().getUser(),
+        'state_change': sci,
+        'transition':   sci.transition,
+        'status':       sci.status,
+        'workflow':     wf,
+        'scripts':      wf.scripts,
+        }
+    return getEngine().getContext(data)
+


=== CMF/DCWorkflow/Guard.py 1.2 => 1.3 ===
 from Products.CMFCore.CMFCorePermissions import ManagePortal
 
-from Expression import Expression, exprNamespace
+from Expression import Expression, StateChangeInfo, createExprContext
 from utils import _dtmldir
 
 
@@ -137,8 +137,8 @@
                 return 0
         expr = self.expr
         if expr is not None:
-            md = exprNamespace(ob, wf_def)
-            res = expr(md)
+            econtext = createExprContext(StateChangeInfo(ob, wf_def))
+            res = expr(econtext)
             if not res:
                 return 0
         return 1


=== CMF/DCWorkflow/States.py 1.2 => 1.3 ===
         {'label': 'Properties', 'action': 'manage_properties'},
         {'label': 'Permissions', 'action': 'manage_permissions'},
-        {'label': 'Variables', 'action': 'manage_vars'},
+        {'label': 'Variables', 'action': 'manage_variables'},
         )
 
     title = ''
@@ -128,13 +128,6 @@
     def getId(self):
         return self.id
 
-    def getVarValues(self):
-        vv = self.var_values
-        if vv is None:
-            return []
-        else:
-            return vv.items()
-
     def getWorkflow(self):
         return aq_parent(aq_inner(aq_parent(aq_inner(self))))
 
@@ -151,7 +144,7 @@
         return self.getWorkflow().transitions.keys()
 
     def getAvailableVarIds(self):
-        return self.getWorkflow().vars.keys()
+        return self.getWorkflow().variables.keys()
 
     def getManagedPermissions(self):
         return list(self.getWorkflow().permissions)
@@ -189,6 +182,77 @@
         self.transitions = tuple(map(str, transitions))
         if REQUEST is not None:
             return self.manage_properties(REQUEST, 'Properties changed.')
+
+
+    _variables_form = DTMLFile('state_variables', _dtmldir)
+
+    def manage_variables(self, REQUEST, manage_tabs_message=None):
+        '''
+        '''
+        return self._variables_form(REQUEST,
+                                     management_view='Variables',
+                                     manage_tabs_message=manage_tabs_message,
+                                     )
+
+    def getVariableValues(self):
+        ''' get VariableValues for management UI
+        '''
+        vv = self.var_values
+        if vv is None:
+            return []
+        else:
+            return vv.items()
+    
+    def getWorkflowVariables(self):
+        ''' get all variables that are available form
+            workflow and not handled yet.
+        '''
+        wf_vars = self.getAvailableVarIds()
+        if self.var_values is None:
+                return wf_vars
+        ret = []
+        for vid in wf_vars:
+            if not self.var_values.has_key(vid):
+                ret.append(vid)
+        return ret
+
+    def addVariable(self,id,value,REQUEST=None):
+        ''' add a WorkflowVariable to State
+        '''
+        if self.var_values is None:
+            self.var_values = PersistentMapping()
+        
+        self.var_values[id] = value
+        
+        if REQUEST is not None:
+            return self.manage_variables(REQUEST, 'Variable added.')
+    
+    def deleteVariables(self,ids=[],REQUEST=None):
+        ''' delete a WorkflowVariable from State
+        '''
+        vv = self.var_values
+        for id in ids:
+            if vv.has_key(id):
+                del vv[id]
+                
+        if REQUEST is not None:
+            return self.manage_variables(REQUEST, 'Variables deleted.')
+
+    def setVariables(self, ids=[], REQUEST=None):
+        ''' set values for Variables set by this state
+        '''
+        if self.var_values is None:
+            self.var_values = PersistentMapping()
+ 
+        vv = self.var_values
+ 
+        if REQUEST is not None:
+            for id in vv.keys():
+                fname = 'varval_%s' % id
+                vv[id] = str(REQUEST[fname])
+            return self.manage_variables(REQUEST, 'Variables changed.')
+
+
 
     _permissions_form = DTMLFile('state_permissions', _dtmldir)
 


=== CMF/DCWorkflow/Transitions.py 1.1 => 1.2 ===
 
 from OFS.SimpleItem import SimpleItem
-from Globals import DTMLFile
+from Globals import DTMLFile, PersistentMapping
 from Acquisition import aq_inner, aq_parent
 import Globals
 from AccessControl import ClassSecurityInfo
@@ -101,6 +101,7 @@
 from ContainerTab import ContainerTab
 from Guard import Guard
 from utils import _dtmldir
+from Expression import Expression
 
 TRIGGER_AUTOMATIC = 0
 TRIGGER_USER_ACTION = 1
@@ -147,7 +148,11 @@
         if not self.var_exprs:
             return ''
         else:
-            return self.var_exprs.get(id, '')
+            expr = self.var_exprs.get(id, None)
+            if expr is not None:
+                return expr.text
+            else:
+                return ''
 
     def getWorkflow(self):
         return aq_parent(aq_inner(aq_parent(aq_inner(self))))
@@ -158,6 +163,9 @@
     def getAvailableScriptIds(self):
         return self.getWorkflow().scripts.keys()
 
+    def getAvailableVarIds(self):
+        return self.getWorkflow().variables.keys()
+
     _properties_form = DTMLFile('transition_properties', _dtmldir)
 
     def manage_properties(self, REQUEST, manage_tabs_message=None):
@@ -190,6 +198,87 @@
         if REQUEST is not None:
             return self.manage_properties(REQUEST, 'Properties changed.')
 
+    _variables_form = DTMLFile('transition_variables', _dtmldir)
+
+    def manage_variables(self, REQUEST, manage_tabs_message=None):
+        '''
+        '''
+        return self._variables_form(REQUEST,
+                                     management_view='Variables',
+                                     manage_tabs_message=manage_tabs_message,
+                                     )
+
+    def getVariableExprs(self):
+        ''' get variable exprs for management UI
+        '''
+        ve = self.var_exprs
+        if ve is None:
+            return []
+        else:
+            ret = []
+            for key in ve.keys():
+                ret.append((key,self.getVarExprText(key)))
+            return ret
+    
+    def getWorkflowVariables(self):
+        ''' get all variables that are available form
+            workflow and not handled yet.
+        '''
+        wf_vars = self.getAvailableVarIds()
+        if self.var_exprs is None:
+                return wf_vars
+        ret = []
+        for vid in wf_vars:
+            if not self.var_exprs.has_key(vid):
+                ret.append(vid)
+        return ret
+
+    def addVariable(self, id, text, REQUEST=None):
+        '''
+        Add a variable expression.
+        '''
+        if self.var_exprs is None:
+            self.var_exprs = PersistentMapping()
+        
+        expr = None
+        if text:
+          expr = Expression(str(text))
+        self.var_exprs[id] = expr
+        
+        if REQUEST is not None:
+            return self.manage_variables(REQUEST, 'Variable added.')
+    
+    def deleteVariables(self,ids=[],REQUEST=None):
+        ''' delete a WorkflowVariable from State
+        '''
+        ve = self.var_exprs
+        for id in ids:
+            if ve.has_key(id):
+                del ve[id]
+                
+        if REQUEST is not None:
+            return self.manage_variables(REQUEST, 'Variables deleted.')
+
+    def setVariables(self, ids=[], REQUEST=None):
+        ''' set values for Variables set by this state
+        '''
+        if self.var_exprs is None:
+            self.var_exprs = PersistentMapping()
+ 
+        ve = self.var_exprs
+ 
+        if REQUEST is not None:
+            for id in ve.keys():
+                fname = 'varexpr_%s' % id
+
+                val = REQUEST[fname]
+                expr = None
+                if val:
+                    expr = Expression(str(REQUEST[fname]))
+                ve[id] = expr
+
+            return self.manage_variables(REQUEST, 'Variables changed.')
+
 Globals.InitializeClass(TransitionDefinition)
 
 
@@ -215,7 +304,7 @@
             manage_tabs_message=manage_tabs_message,
             )
 
-    def addTransition(self, id, REQUEST=None):   #, RESPONSE=None):
+    def addTransition(self, id, REQUEST=None):
         '''
         '''
         tdef = TransitionDefinition(id)


=== CMF/DCWorkflow/Variables.py 1.1 => 1.2 ===
         if default_expr:
             self.default_expr = Expression(default_expr)
+        else:
+            self.default_expr = None
+            
         g = Guard()
         if g.changeFromProperties(props or REQUEST):
             self.info_guard = g