[Zope3-checkins] CVS: Zope3/src/zope/app/workflow/stateful - testobject.py:1.1 testobject.pyc:1.1 testobject.zcml:1.1 configure.zcml:1.4 definition.py:1.6 instance.py:1.9 xmlexport_template.pt:1.2 xmlimportexport.py:1.6

Stephan Richter srichter@cosmos.phy.tufts.edu
Thu, 31 Jul 2003 11:02:15 -0400


Update of /cvs-repository/Zope3/src/zope/app/workflow/stateful
In directory cvs.zope.org:/tmp/cvs-serv10755/src/zope/app/workflow/stateful

Modified Files:
	configure.zcml definition.py instance.py xmlexport_template.pt 
	xmlimportexport.py 
Added Files:
	testobject.py testobject.pyc testobject.zcml 
Log Message:
Implemented relevant data for Processes (read workflows). You can specify
a schema which contains the fields that should be available as relevant 
data. Once the schema is set, you are able to specify "get" and "set" 
permissions for each field. That;s it for the setup.

When you then create a Content Object that uses the process definition for
which you declared the schema, you will see that there is a form in the 
'Workflows' tab of this content object that allows you to manipulate the
releavant data.

I also enhanced the XML Import/Export to support relevant data and their 
permissions. 

The easiest way to check all this functionality out is to uncomment the 
include directives in browser/workflow/stateful/configure.zcml and 
workflow/stateful/configure.zcml (both at the end of the file).

This completes my code and status review of the workflow package, which I
think is now ready for the beta.


=== Added File Zope3/src/zope/app/workflow/stateful/testobject.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Stateful Workflow Test Object

This represents a simple content object that can receive workflows.

$Id: testobject.py,v 1.1 2003/07/31 15:01:36 srichter Exp $
"""

from persistence import Persistent
from zope.interface import Interface, implements
from zope.schema import TextLine, Int

class ITestObject(Interface):

    test = TextLine(
        title=u"Test Attribute",
        description=u"This is a test attribute.",
        default=u"foo",
        required=True)

class IWorkflowData(Interface):

    title = TextLine(
        title=u"Title",
        required=True)

    number = Int(
        title=u"Number",
        required=True)


class TestObject(Persistent):
    implements(ITestObject)

    test = u"foo"



=== Added File Zope3/src/zope/app/workflow/stateful/testobject.pyc ===
  <Binary-ish file>

=== Added File Zope3/src/zope/app/workflow/stateful/testobject.zcml ===
<zopeConfigure
   xmlns="http://namespaces.zope.org/zope">

  <interface interface=".testobject.IWorkflowData"/>

  <content class=".testobject.TestObject">

    <implements
       interface="zope.app.interfaces.annotation.IAttributeAnnotatable"/>

    <implements interface=
        "zope.app.interfaces.workflow.IProcessInstanceContainerAdaptable"/>

    <factory
        id="TestObject"
        permission="zope.ManageContent"
        title="Test Object"
        description="Test Object" />

    <require
        permission="zope.View"
        interface=".testobject.ITestObject"
        set_schema=".testobject.ITestObject" />

  </content>

</zopeConfigure>


=== Zope3/src/zope/app/workflow/stateful/configure.zcml 1.3 => 1.4 ===
--- Zope3/src/zope/app/workflow/stateful/configure.zcml:1.3	Tue Jul 29 20:00:25 2003
+++ Zope3/src/zope/app/workflow/stateful/configure.zcml	Thu Jul 31 11:01:36 2003
@@ -73,7 +73,8 @@
       />
   <require
       permission="zope.workflow.ManageProcessDefinitions"
-      interface="zope.app.interfaces.workflow.stateful.IStatefulTransitionsContainer" 
+      interface=
+          "zope.app.interfaces.workflow.stateful.IStatefulTransitionsContainer" 
       />
   <implements 
       interface="zope.app.interfaces.annotation.IAttributeAnnotatable" 
@@ -122,5 +123,8 @@
    interface="zope.app.interfaces.workflow.stateful.IStatefulProcessDefinition"
    factory=".xmlimportexport.XMLExportHandler"
    />
+
+<!-- Test Object for testing Stateful Workflows -->
+<!--include file="testobject.zcml"/-->
 
 </zopeConfigure>


=== Zope3/src/zope/app/workflow/stateful/definition.py 1.5 => 1.6 ===
--- Zope3/src/zope/app/workflow/stateful/definition.py:1.5	Thu Jun  5 08:03:19 2003
+++ Zope3/src/zope/app/workflow/stateful/definition.py	Thu Jul 31 11:01:36 2003
@@ -19,6 +19,7 @@
 __metaclass__ = type
 
 from persistence import Persistent
+from persistence.dict import PersistentDict
 
 from zope.app.context import ContextWrapper
 from zope.context import getWrapperContainer
@@ -27,7 +28,7 @@
 from zope.app.interfaces.container import IReadContainer
 
 from zope.app.interfaces.workflow.stateful import IStatefulProcessDefinition
-from zope.app.interfaces.workflow.stateful import IState, ITransition
+from zope.app.interfaces.workflow.stateful import IState, ITransition, INITIAL
 from zope.app.interfaces.workflow.stateful import IStatefulStatesContainer
 from zope.app.interfaces.workflow.stateful import IStatefulTransitionsContainer
 
@@ -39,19 +40,16 @@
 
 class State(Persistent):
     """State."""
-
     implements(IState)
 
 
-
 class StatesContainer(ProcessDefinitionElementContainer):
-    """Container that stores States.
-    """
+    """Container that stores States."""
     implements(IStatefulStatesContainer)
 
 
 class Transition(Persistent):
-    """Transition."""
+    """Transition from one state to another."""
 
     implements(ITransition)
 
@@ -65,7 +63,6 @@
         self.__permission = permission or None
         self.__triggerMode = triggerMode
 
-
     def getSourceState(self):
         return self.__source
 
@@ -127,8 +124,7 @@
 
 
 class TransitionsContainer(ProcessDefinitionElementContainer):
-    """Container that stores Transitions.
-    """
+    """Container that stores Transitions."""
     implements(IStatefulTransitionsContainer)
 
 
@@ -144,79 +140,78 @@
         self.__states.setObject(self.getInitialStateName(), initial)
         self.__transitions = TransitionsContainer()
         self.__schema = None
+        # See workflow.stateful.IStatefulProcessDefinition
+        self.schemaPermissions = PersistentDict()
 
     _clear = clear = __init__
 
-    ############################################################
-    # Implementation methods for interface
-    # zope.app.interfaces.workflow.stateful.IStatefulProcessDefinition
-
     def getRelevantDataSchema(self):
         return self.__schema
 
     def setRelevantDataSchema(self, schema):
         self.__schema = schema
 
+    # See workflow.stateful.IStatefulProcessDefinition
     relevantDataSchema = property(getRelevantDataSchema,
                                   setRelevantDataSchema,
                                   None,
                                   "Schema for RelevantData.")
 
-
-
+    # See workflow.stateful.IStatefulProcessDefinition
     states = property(lambda self: self.__states)
 
+    # See workflow.stateful.IStatefulProcessDefinition
     transitions = property(lambda self: self.__transitions)
 
     def addState(self, name, state):
+        """See workflow.stateful.IStatefulProcessDefinition"""
         if name in self.states:
             raise KeyError, name
         self.states.setObject(name, state)
 
     def getState(self, name):
+        """See workflow.stateful.IStatefulProcessDefinition"""
         return self.states[name]
     getState = ContextMethod(getState)
 
     def removeState(self, name):
+        """See workflow.stateful.IStatefulProcessDefinition"""
         del self.states[name]
 
     def getStateNames(self):
+        """See workflow.stateful.IStatefulProcessDefinition"""
         return self.states.keys()
 
-    # XXX This shouldn't be hardcoded
     def getInitialStateName(self):
-        return 'INITIAL'
+        """See workflow.stateful.IStatefulProcessDefinition"""
+        return INITIAL
 
     def addTransition(self, name, transition):
+        """See workflow.stateful.IStatefulProcessDefinition"""
         if name in self.transitions:
             raise KeyError, name
         self.transitions.setObject(name, transition)
 
     def getTransition(self, name):
+        """See workflow.stateful.IStatefulProcessDefinition"""
         return self.transitions[name]
     getTransition = ContextMethod(getTransition)
 
     def removeTransition(self, name):
+        """See workflow.stateful.IStatefulProcessDefinition"""
         del self.transitions[name]
 
     def getTransitionNames(self):
+        """See workflow.stateful.IStatefulProcessDefinition"""
         return self.transitions.keys()
 
-    # IProcessDefinition
-
     def createProcessInstance(self, definition_name):
+        """See workflow.IProcessDefinition"""
         pi_obj = StatefulProcessInstance(definition_name)
         ContextWrapper(pi_obj, self).initialize()
         return pi_obj
     createProcessInstance = ContextMethod(createProcessInstance)
 
-    #
-    ############################################################
-
-
-    ############################################################
-    # Implementation methods for interface
-    # zope.app.interfaces.container.IReadContainer
 
     def __getitem__(self, key):
         "See Interface.Common.Mapping.IReadMapping"
@@ -247,26 +242,25 @@
 
         return self.get(key) is not None
 
-    # Enumeration methods. We'll only expose Packages for now:
     def __iter__(self):
+        """See zope.app.interfaces.container.IReadContainer"""
         return iter(self.keys())
 
     def keys(self):
+        """See zope.app.interfaces.container.IReadContainer"""
         return ['states', 'transitions']
 
     def values(self):
+        """See zope.app.interfaces.container.IReadContainer"""
         return map(self.get, self.keys())
-
     values = ContextMethod(values)
 
     def items(self):
+        """See zope.app.interfaces.container.IReadContainer"""
         return [(key, self.get(key)) for key in self.keys()]
-
     items = ContextMethod(items)
 
     def __len__(self):
+        """See zope.app.interfaces.container.IReadContainer"""
         return 2
 
-
-    #
-    ############################################################


=== Zope3/src/zope/app/workflow/stateful/instance.py 1.8 => 1.9 ===
--- Zope3/src/zope/app/workflow/stateful/instance.py:1.8	Wed Jul 30 11:24:09 2003
+++ Zope3/src/zope/app/workflow/stateful/instance.py	Thu Jul 31 11:01:36 2003
@@ -18,6 +18,7 @@
 __metaclass__ = type
 
 from persistence import Persistent
+from persistence.dict import PersistentDict
 
 from zope.app.context import ContextWrapper
 from zope.app.event import publish
@@ -39,7 +40,8 @@
 from zope.proxy import removeAllProxies
 from zope.schema import getFields
 from zope.security.management import getSecurityManager
-from zope.security.checker import CheckerPublic
+from zope.security.checker import CheckerPublic, Checker
+from zope.security.proxy import Proxy
 from zope.tales.engine import Engine
 
 
@@ -79,9 +81,12 @@
 
 class RelevantData(Persistent):
     """The relevant data object can store data that is important to the
-    workflow and fires events when this data is changed."""
+    workflow and fires events when this data is changed.
 
-    def __init__(self, schema=None):
+    If you don't understand this code, don't worry, it is heavy lifting.
+    """
+
+    def __init__(self, schema=None, schemaPermissions=None):
         super(RelevantData, self).__init__()
         self.__schema = None
         # Add the new attributes, if there was a schema passed in
@@ -91,9 +96,26 @@
             self.__schema = schema
             directlyProvides(self, schema)
 
+            # Build up a Checker rules and store it for later
+            self.__checker_getattr = PersistentDict()
+            self.__checker_setattr = PersistentDict()
+            for name in getFields(schema):
+                get_perm, set_perm = schemaPermissions.get(name, (None, None))
+                self.__checker_getattr[name] = get_perm or CheckerPublic
+                self.__checker_setattr[name] = set_perm or CheckerPublic
+
+            # Always permit our class's two public methods
+            self.__checker_getattr['getChecker'] = CheckerPublic
+            self.__checker_getattr['getSchema'] = CheckerPublic
+
+
     def __setattr__(self, key, value):
         # The '__schema' attribute has a sepcial function
-        if key == '_RelevantData__schema':
+        if key in ('_RelevantData__schema',
+                   '_RelevantData__checker_getattr',
+                   '_RelevantData__checker_setattr',
+                   'getChecker', 'getSchema') or \
+               key.startswith('_p_'):
             return super(RelevantData, self).__setattr__(key, value)
             
         is_schema_field = self.__schema is not None and \
@@ -114,6 +136,13 @@
                 process, self.__schema, key, oldvalue, value))
     __setattr__ = ContextMethod(__setattr__)
 
+    def getChecker(self):
+        return Checker(self.__checker_getattr.get,
+                       self.__checker_setattr.get)
+
+    def getSchema(self):
+        return self.__schema
+
 
 class StateChangeInfo:
     """Immutable StateChangeInfo."""
@@ -133,7 +162,9 @@
     implements(IStatefulProcessInstance)
 
     def getData(self):
-        return ContextWrapper(self._data, self, name="data")
+        # Always give out the data attribute as proxied object.
+        data = Proxy(self._data, self._data.getChecker())
+        return ContextWrapper(data, self, name="data")
 
     data = ContextProperty(getData) 
 
@@ -147,12 +178,8 @@
         # This should really always return a schema
         schema = clean_pd.getRelevantDataSchema()
         if schema:
-            if isinstance(schema, (str, unicode)):
-                sm = getServiceManager(self)
-                schema = sm.resolve(schema)
-
             # create relevant-data
-            self._data = RelevantData(schema)
+            self._data = RelevantData(schema, clean_pd.schemaPermissions)
         else:
             self._data = None
         # setup permission on data


=== Zope3/src/zope/app/workflow/stateful/xmlexport_template.pt 1.1 => 1.2 ===
--- Zope3/src/zope/app/workflow/stateful/xmlexport_template.pt:1.1	Thu May  8 13:27:19 2003
+++ Zope3/src/zope/app/workflow/stateful/xmlexport_template.pt	Thu Jul 31 11:01:36 2003
@@ -6,7 +6,14 @@
 
   <schema 
     name = ""
-    tal:attributes="name wf/getRelevantDataSchema">
+    tal:attributes="name view/relevantDataSchema">
+    <permissions>
+      <permission for="title" type="set" id="zope.Public"
+          tal:repeat="perm view/getSchemaPermissions"
+          tal:attributes="for perm/fieldName;
+                          type perm/type;
+                          id perm/id"/>
+    </permissions>
   </schema>
 
   <states>
@@ -14,8 +21,9 @@
       title = ""
       name = ""
       tal:repeat="state wf/getStateNames"
-      tal:attributes="title python:view.getDublinCore(wf.getState(state)).Title();
-                      name  state"></state>
+      tal:attributes="
+          title python:view.getDublinCore(wf.getState(state)).Title();
+          name  state"></state>
   </states>
 
   <transitions>
@@ -30,14 +38,15 @@
         title = ""
         name = ""
         tal:define="transObj python:wf.getTransition(trans)"
-        tal:attributes="sourceState      transObj/getSourceState;
-                        destinationState transObj/getDestinationState;
-                        condition        transObj/getCondition;
-                        script           transObj/getScript;
-                        permission       python:view.getPermissionId(transObj.getPermission());
-                        triggerMode      transObj/getTriggerMode;
-                        title            python:view.getDublinCore(transObj).Title();
-                        name             trans"></transition>
+        tal:attributes="
+            sourceState      transObj/getSourceState;
+            destinationState transObj/getDestinationState;
+            condition        transObj/getCondition;
+            script           transObj/getScript;
+            permission   python:view.getPermissionId(transObj.getPermission());
+            triggerMode      transObj/getTriggerMode;
+            title            python:view.getDublinCore(transObj).Title();
+            name             trans"/>
     </tal:block>
   </transitions>
   


=== Zope3/src/zope/app/workflow/stateful/xmlimportexport.py 1.5 => 1.6 ===
--- Zope3/src/zope/app/workflow/stateful/xmlimportexport.py:1.5	Fri Jun  6 15:29:07 2003
+++ Zope3/src/zope/app/workflow/stateful/xmlimportexport.py	Thu Jul 31 11:01:36 2003
@@ -15,25 +15,26 @@
 
 $Id$
 """
-__metaclass__ = type
-
+from xml.sax import parse
+from xml.sax.handler import ContentHandler
 
-from zope.app.pagetemplate.viewpagetemplatefile \
-     import ViewPageTemplateFile
+from zope.app.interfaces.dublincore import IZopeDublinCore
 from zope.app.interfaces.workflow.stateful \
      import IStatefulProcessDefinition
 from zope.app.interfaces.workflow import IProcessDefinitionImportHandler
 from zope.app.interfaces.workflow import IProcessDefinitionExportHandler
-from zope.component import getAdapter
-from zope.app.interfaces.dublincore import IZopeDublinCore
+from zope.app.pagetemplate.viewpagetemplatefile \
+     import ViewPageTemplateFile
+from zope.app.services.servicenames import Permissions
+from zope.app.workflow.stateful.definition import State, Transition
+from zope.component import getAdapter, getService
+from zope.configuration.name import resolve
+from zope.interface import implements
 from zope.proxy import removeAllProxies
 from zope.security.checker import CheckerPublic
+from zope.security.proxy import trustedRemoveSecurityProxy
 
-from xml.sax import parse
-from xml.sax.handler import ContentHandler
-
-from zope.app.workflow.stateful.definition import State, Transition
-from zope.interface import implements
+__metaclass__ = type
 
 
 # basic implementation for a format-checker
@@ -57,6 +58,7 @@
     def __init__(self, context, encoding='latin-1'):
         self.context = context
         self.encoding = encoding
+        self.perm_service = getService(self.context, Permissions)
 
     def startElement(self, name, attrs):
         handler = getattr(self, 'start' + name.title().replace('-', ''), None)
@@ -75,14 +77,34 @@
 
     startStates = noop
     startTransitions = noop
+    startPermissions = noop
 
     def startWorkflow(self, attrs):
         dc = getAdapter(self.context, IZopeDublinCore)
         dc.title = attrs.get('title', u'')
 
     def startSchema(self, attrs):
-        name = attrs['name'].encode(self.encoding)
-        self.context.setRelevantDataSchema(name)
+        name = attrs['name'].encode(self.encoding).strip()
+        if name:
+            self.context.relevantDataSchema = resolve(name)
+
+    def startPermission(self, attrs):
+        perms = trustedRemoveSecurityProxy(self.context.schemaPermissions)
+        fieldName = attrs.get('for')
+        type = attrs.get('type')
+        perm_id = attrs.get('id')
+        if perm_id == 'zope.Public':
+            perm = CheckerPublic
+        elif perm_id == '':
+            perm = None
+        else:
+            perm = self.perm_service.getPermission(perm_id)
+        if not fieldName in perms.keys():
+            perms[fieldName] = (CheckerPublic, CheckerPublic)
+        if type == u'get':
+            perms[fieldName] = (perm, perms[fieldName][1])
+        if type == u'set':
+            perms[fieldName] = (perms[fieldName][0], perm)
 
     def startState(self, attrs):
         encoding = self.encoding
@@ -162,3 +184,20 @@
             return ''
         return removeAllProxies(permission).getId()
 
+    def getSchemaPermissions(self):
+        info = []
+        perms = self.getDefinition().schemaPermissions
+        for field, (getPerm, setPerm) in perms.items():
+            info.append({'fieldName': field,
+                         'type': 'get',
+                         'id': self.getPermissionId(getPerm)})
+            info.append({'fieldName': field,
+                         'type': 'set',
+                         'id': self.getPermissionId(setPerm)})
+        return info
+
+    def relevantDataSchema(self):
+        schema = self.getDefinition().relevantDataSchema
+        if schema is None:
+            return 'None'
+        return schema.__module__ + '.' + schema.__name__