diff --git a/fields/workflow.py b/fields/workflow.py
new file mode 100644
index 0000000..a175c42
--- /dev/null
+++ b/fields/workflow.py
@@ -0,0 +1,329 @@
+# ------------------------------------------------------------------------------
+# This file is part of Appy, a framework for building applications in the Python
+# language. Copyright (C) 2007 Gaetan Delannay
+
+# Appy is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+
+# Appy is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License along with
+# Appy. If not, see .
+# ------------------------------------------------------------------------------
+import types, string
+from appy.gen.mail import sendNotification
+
+# Default Appy permissions -----------------------------------------------------
+r, w, d = ('read', 'write', 'delete')
+
+# ------------------------------------------------------------------------------
+class Role:
+ '''Represents a role, be it local or global.'''
+ zopeRoles = ('Manager', 'Owner', 'Anonymous', 'Authenticated')
+ zopeLocalRoles = ('Owner',)
+ zopeUngrantableRoles = ('Anonymous', 'Authenticated')
+ def __init__(self, name, local=False, grantable=True):
+ self.name = name
+ self.local = local # True if it can be used as local role only.
+ # It is a standard Zope role or an application-specific one?
+ self.zope = name in self.zopeRoles
+ if self.zope and (name in self.zopeLocalRoles):
+ self.local = True
+ self.grantable = grantable
+ if self.zope and (name in self.zopeUngrantableRoles):
+ self.grantable = False
+ # An ungrantable role is one that is, like the Anonymous or
+ # Authenticated roles, automatically attributed to a user.
+
+# ------------------------------------------------------------------------------
+class State:
+ '''Represents a workflow state.'''
+ def __init__(self, permissions, initial=False, phase=None, show=True):
+ self.usedRoles = {}
+ # The following dict ~{s_permissionName:[s_roleName|Role_role]}~
+ # gives, for every permission managed by a workflow, the list of roles
+ # for which the permission is granted in this state. Standard
+ # permissions are 'read', 'write' and 'delete'.
+ self.permissions = permissions
+ self.initial = initial
+ self.phase = phase
+ self.show = show
+ # Standardize the way roles are expressed within self.permissions
+ self.standardizeRoles()
+
+ def getName(self, wf):
+ '''Returns the name for this state in workflow p_wf.'''
+ for name in dir(wf):
+ value = getattr(wf, name)
+ if (value == self): return name
+
+ def getRole(self, role):
+ '''p_role can be the name of a role or a Role instance. If it is the
+ name of a role, this method returns self.usedRoles[role] if it
+ exists, or creates a Role instance, puts it in self.usedRoles and
+ returns it else. If it is a Role instance, the method stores it in
+ self.usedRoles if it is not in it yet and returns it.'''
+ if isinstance(role, basestring):
+ if role in self.usedRoles:
+ return self.usedRoles[role]
+ else:
+ theRole = Role(role)
+ self.usedRoles[role] = theRole
+ return theRole
+ else:
+ if role.name not in self.usedRoles:
+ self.usedRoles[role.name] = role
+ return role
+
+ def standardizeRoles(self):
+ '''This method converts, within self.permissions, every role to a
+ Role instance. Every used role is stored in self.usedRoles.'''
+ for permission, roles in self.permissions.items():
+ if isinstance(roles, basestring) or isinstance(roles, Role):
+ self.permissions[permission] = [self.getRole(roles)]
+ elif roles:
+ rolesList = []
+ for role in roles:
+ rolesList.append(self.getRole(role))
+ self.permissions[permission] = rolesList
+
+ def getUsedRoles(self): return self.usedRoles.values()
+
+# ------------------------------------------------------------------------------
+class Transition:
+ '''Represents a workflow transition.'''
+ def __init__(self, states, condition=True, action=None, notify=None,
+ show=True, confirm=False):
+ self.states = states # In its simpler form, it is a tuple with 2
+ # states: (fromState, toState). But it can also be a tuple of several
+ # (fromState, toState) sub-tuples. This way, you may define only 1
+ # transition at several places in the state-transition diagram. It may
+ # be useful for "undo" transitions, for example.
+ self.condition = condition
+ if isinstance(condition, basestring):
+ # The condition specifies the name of a role.
+ self.condition = Role(condition)
+ self.action = action
+ self.notify = notify # If not None, it is a method telling who must be
+ # notified by email after the transition has been executed.
+ self.show = show # If False, the end user will not be able to trigger
+ # the transition. It will only be possible by code.
+ self.confirm = confirm # If True, a confirm popup will show up.
+
+ def getName(self, wf):
+ '''Returns the name for this state in workflow p_wf.'''
+ for name in dir(wf):
+ value = getattr(wf, name)
+ if (value == self): return name
+
+ def getUsedRoles(self):
+ '''self.condition can specify a role.'''
+ res = []
+ if isinstance(self.condition, Role):
+ res.append(self.condition)
+ return res
+
+ def isSingle(self):
+ '''If this transition is only defined between 2 states, returns True.
+ Else, returns False.'''
+ return isinstance(self.states[0], State)
+
+ def isShowable(self, workflow, obj):
+ '''Is this transition showable?'''
+ if callable(self.show):
+ return self.show(workflow, obj.appy())
+ else:
+ return self.show
+
+ def hasState(self, state, isFrom):
+ '''If p_isFrom is True, this method returns True if p_state is a
+ starting state for p_self. If p_isFrom is False, this method returns
+ True if p_state is an ending state for p_self.'''
+ stateIndex = 1
+ if isFrom:
+ stateIndex = 0
+ if self.isSingle():
+ res = state == self.states[stateIndex]
+ else:
+ res = False
+ for states in self.states:
+ if states[stateIndex] == state:
+ res = True
+ break
+ return res
+
+ def isTriggerable(self, obj, wf, noSecurity=False):
+ '''Can this transition be triggered on p_obj?'''
+ wf = wf.__instance__ # We need the prototypical instance here.
+ # Checks that the current state of the object is a start state for this
+ # transition.
+ objState = obj.State(name=False)
+ if self.isSingle():
+ if objState != self.states[0]: return False
+ else:
+ startFound = False
+ for startState, stopState in self.states:
+ if startState == objState:
+ startFound = True
+ break
+ if not startFound: return False
+ # Check that the condition is met, excepted if noSecurity is True.
+ if noSecurity: return True
+ user = obj.getTool().getUser()
+ if isinstance(self.condition, Role):
+ # Condition is a role. Transition may be triggered if the user has
+ # this role.
+ return user.has_role(self.condition.name, obj)
+ elif type(self.condition) == types.FunctionType:
+ return self.condition(wf, obj.appy())
+ elif type(self.condition) in (tuple, list):
+ # It is a list of roles and/or functions. Transition may be
+ # triggered if user has at least one of those roles and if all
+ # functions return True.
+ hasRole = None
+ for roleOrFunction in self.condition:
+ if isinstance(roleOrFunction, basestring):
+ if hasRole == None:
+ hasRole = False
+ if user.has_role(roleOrFunction, obj):
+ hasRole = True
+ elif type(roleOrFunction) == types.FunctionType:
+ if not roleOrFunction(wf, obj.appy()):
+ return False
+ if hasRole != False:
+ return True
+
+ def executeAction(self, obj, wf):
+ '''Executes the action related to this transition.'''
+ msg = ''
+ obj = obj.appy()
+ wf = wf.__instance__ # We need the prototypical instance here.
+ if type(self.action) in (tuple, list):
+ # We need to execute a list of actions
+ for act in self.action:
+ msgPart = act(wf, obj)
+ if msgPart: msg += msgPart
+ else: # We execute a single action only.
+ msgPart = self.action(wf, obj)
+ if msgPart: msg += msgPart
+ return msg
+
+ def trigger(self, transitionName, obj, wf, comment, doAction=True,
+ doNotify=True, doHistory=True, doSay=True):
+ '''This method triggers this transition on p_obj. The transition is
+ supposed to be triggerable (call to self.isTriggerable must have been
+ performed before calling this method). If p_doAction is False, the
+ action that must normally be executed after the transition has been
+ triggered will not be executed. If p_doNotify is False, the
+ email notifications that must normally be launched after the
+ transition has been triggered will not be launched. If p_doHistory is
+ False, there will be no trace from this transition triggering in the
+ workflow history. If p_doSay is False, we consider the transition is
+ trigger programmatically, and no message is returned to the user.'''
+ # Create the workflow_history dict if it does not exist.
+ if not hasattr(obj.aq_base, 'workflow_history'):
+ from persistent.mapping import PersistentMapping
+ obj.workflow_history = PersistentMapping()
+ # Create the event list if it does not exist in the dict
+ if not obj.workflow_history: obj.workflow_history['appy'] = ()
+ # Get the key where object history is stored (this overstructure is
+ # only there for backward compatibility reasons)
+ key = obj.workflow_history.keys()[0]
+ # Identify the target state for this transition
+ if self.isSingle():
+ targetState = self.states[1]
+ targetStateName = targetState.getName(wf)
+ else:
+ startState = obj.State(name=False)
+ for sState, tState in self.states:
+ if startState == sState:
+ targetState = tState
+ targetStateName = targetState.getName(wf)
+ break
+ # Create the event and add it in the object history
+ action = transitionName
+ if transitionName == '_init_': action = None
+ if not doHistory: comment = '_invisible_'
+ obj.addHistoryEvent(action, review_state=targetStateName,
+ comments=comment)
+ # Reindex the object if required. Not only security-related indexes
+ # (Allowed, State) need to be updated here.
+ if not obj.isTemporary(): obj.reindex()
+ # Execute the related action if needed
+ msg = ''
+ if doAction and self.action: msg = self.executeAction(obj, wf)
+ # Send notifications if needed
+ if doNotify and self.notify and obj.getTool(True).mailEnabled:
+ sendNotification(obj.appy(), self, transitionName, wf)
+ # Return a message to the user if needed
+ if not doSay or (transitionName == '_init_'): return
+ if not msg: msg = obj.translate('object_saved')
+ obj.say(msg)
+
+# ------------------------------------------------------------------------------
+class Permission:
+ '''If you need to define a specific read or write permission for some field
+ on a gen-class, you use the specific boolean attrs
+ "specificReadPermission" or "specificWritePermission". When you want to
+ refer to those specific read or write permissions when
+ defining a workflow, for example, you need to use instances of
+ "ReadPermission" and "WritePermission", the 2 children classes of this
+ class. For example, if you need to refer to write permission of
+ attribute "t1" of class A, write: WritePermission("A.t1") or
+ WritePermission("x.y.A.t1") if class A is not in the same module as
+ where you instantiate the class.
+
+ Note that this holds only if you use attributes "specificReadPermission"
+ and "specificWritePermission" as booleans. When defining named
+ (string) permissions, for referring to it you simply use those strings,
+ you do not create instances of ReadPermission or WritePermission.'''
+
+ allowedChars = string.digits + string.letters + '_'
+
+ def __init__(self, fieldDescriptor):
+ self.fieldDescriptor = fieldDescriptor
+
+ def getName(self, wf, appName):
+ '''Returns the name of this permission.'''
+ className, fieldName = self.fieldDescriptor.rsplit('.', 1)
+ if className.find('.') == -1:
+ # The related class resides in the same module as the workflow
+ fullClassName= '%s_%s' % (wf.__module__.replace('.', '_'),className)
+ else:
+ # className contains the full package name of the class
+ fullClassName = className.replace('.', '_')
+ # Read or Write ?
+ if self.__class__.__name__ == 'ReadPermission': access = 'Read'
+ else: access = 'Write'
+ return '%s: %s %s %s' % (appName, access, fullClassName, fieldName)
+
+class ReadPermission(Permission): pass
+class WritePermission(Permission): pass
+
+# Standard workflows -----------------------------------------------------------
+class WorkflowAnonymous:
+ '''One-state workflow allowing anyone to consult and Manager to edit.'''
+ mgr = 'Manager'
+ o = 'Owner'
+ active = State({r:(mgr, 'Anonymous', 'Authenticated'), w:(mgr,o),d:(mgr,o)},
+ initial=True)
+
+class WorkflowAuthenticated:
+ '''One-state workflow allowing authenticated users to consult and Manager
+ to edit.'''
+ mgr = 'Manager'
+ o = 'Owner'
+ active = State({r:(mgr, 'Authenticated'), w:(mgr,o), d:(mgr,o)},
+ initial=True)
+
+class WorkflowOwner:
+ '''One-state workflow allowing only manager and owner to consult and
+ edit.'''
+ mgr = 'Manager'
+ o = 'Owner'
+ active = State({r:(mgr, o), w:(mgr, o), d:mgr}, initial=True)
+# ------------------------------------------------------------------------------
diff --git a/gen/__init__.py b/gen/__init__.py
index 34760c3..1f28963 100644
--- a/gen/__init__.py
+++ b/gen/__init__.py
@@ -1,12 +1,24 @@
# ------------------------------------------------------------------------------
-import types, string
-from appy.gen.mail import sendNotification
-from appy.gen import utils as gutils
+# This file is part of Appy, a framework for building applications in the Python
+# language. Copyright (C) 2007 Gaetan Delannay
+
+# Appy is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+
+# Appy is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License along with
+# Appy. If not, see .
# ------------------------------------------------------------------------------
# Import stuff from appy.fields (and from a few other places too).
# This way, when an app gets "from appy.gen import *", everything is available.
-# ------------------------------------------------------------------------------
+from appy import Object
+from appy.px import Px
from appy.fields import Field
from appy.fields.action import Action
from appy.fields.boolean import Boolean
@@ -24,13 +36,9 @@ from appy.fields.search import Search, UiSearch
from appy.fields.group import Group, Column
from appy.fields.page import Page
from appy.fields.phase import Phase
+from appy.fields.workflow import *
from appy.gen.layout import Table
-from appy.px import Px
-from appy import Object
-No = gutils.No
-
-# Default Appy permissions -----------------------------------------------------
-r, w, d = ('read', 'write', 'delete')
+from appy.gen.utils import No
class Import:
'''Used for describing the place where to find the data to use for creating
@@ -58,307 +66,6 @@ class Import:
# and must return a similar, sorted, list.
self.sort = sort
-# Workflow-specific types and default workflows --------------------------------
-class Role:
- '''Represents a role.'''
- zopeRoles = ('Manager', 'Owner', 'Anonymous', 'Authenticated')
- zopeLocalRoles = ('Owner',)
- zopeUngrantableRoles = ('Anonymous', 'Authenticated')
- def __init__(self, name, local=False, grantable=True):
- self.name = name
- self.local = local # True if it can be used as local role only.
- # It is a standard Zope role or an application-specific one?
- self.zope = name in self.zopeRoles
- if self.zope and (name in self.zopeLocalRoles):
- self.local = True
- self.grantable = grantable
- if self.zope and (name in self.zopeUngrantableRoles):
- self.grantable = False
- # An ungrantable role is one that is, like the Anonymous or
- # Authenticated roles, automatically attributed to a user.
-
-class State:
- def __init__(self, permissions, initial=False, phase=None, show=True):
- self.usedRoles = {}
- # The following dict ~{s_permissionName:[s_roleName|Role_role]}~
- # gives, for every permission managed by a workflow, the list of roles
- # for which the permission is granted in this state. Standard
- # permissions are 'read', 'write' and 'delete'.
- self.permissions = permissions
- self.initial = initial
- self.phase = phase
- self.show = show
- # Standardize the way roles are expressed within self.permissions
- self.standardizeRoles()
-
- def getName(self, wf):
- '''Returns the name for this state in workflow p_wf.'''
- for name in dir(wf):
- value = getattr(wf, name)
- if (value == self): return name
-
- def getRole(self, role):
- '''p_role can be the name of a role or a Role instance. If it is the
- name of a role, this method returns self.usedRoles[role] if it
- exists, or creates a Role instance, puts it in self.usedRoles and
- returns it else. If it is a Role instance, the method stores it in
- self.usedRoles if it is not in it yet and returns it.'''
- if isinstance(role, basestring):
- if role in self.usedRoles:
- return self.usedRoles[role]
- else:
- theRole = Role(role)
- self.usedRoles[role] = theRole
- return theRole
- else:
- if role.name not in self.usedRoles:
- self.usedRoles[role.name] = role
- return role
-
- def standardizeRoles(self):
- '''This method converts, within self.permissions, every role to a
- Role instance. Every used role is stored in self.usedRoles.'''
- for permission, roles in self.permissions.items():
- if isinstance(roles, basestring) or isinstance(roles, Role):
- self.permissions[permission] = [self.getRole(roles)]
- elif roles:
- rolesList = []
- for role in roles:
- rolesList.append(self.getRole(role))
- self.permissions[permission] = rolesList
-
- def getUsedRoles(self): return self.usedRoles.values()
-
-class Transition:
- def __init__(self, states, condition=True, action=None, notify=None,
- show=True, confirm=False):
- self.states = states # In its simpler form, it is a tuple with 2
- # states: (fromState, toState). But it can also be a tuple of several
- # (fromState, toState) sub-tuples. This way, you may define only 1
- # transition at several places in the state-transition diagram. It may
- # be useful for "undo" transitions, for example.
- self.condition = condition
- if isinstance(condition, basestring):
- # The condition specifies the name of a role.
- self.condition = Role(condition)
- self.action = action
- self.notify = notify # If not None, it is a method telling who must be
- # notified by email after the transition has been executed.
- self.show = show # If False, the end user will not be able to trigger
- # the transition. It will only be possible by code.
- self.confirm = confirm # If True, a confirm popup will show up.
-
- def getName(self, wf):
- '''Returns the name for this state in workflow p_wf.'''
- for name in dir(wf):
- value = getattr(wf, name)
- if (value == self): return name
-
- def getUsedRoles(self):
- '''self.condition can specify a role.'''
- res = []
- if isinstance(self.condition, Role):
- res.append(self.condition)
- return res
-
- def isSingle(self):
- '''If this transition is only defined between 2 states, returns True.
- Else, returns False.'''
- return isinstance(self.states[0], State)
-
- def isShowable(self, workflow, obj):
- '''Is this transition showable?'''
- if callable(self.show):
- return self.show(workflow, obj.appy())
- else:
- return self.show
-
- def hasState(self, state, isFrom):
- '''If p_isFrom is True, this method returns True if p_state is a
- starting state for p_self. If p_isFrom is False, this method returns
- True if p_state is an ending state for p_self.'''
- stateIndex = 1
- if isFrom:
- stateIndex = 0
- if self.isSingle():
- res = state == self.states[stateIndex]
- else:
- res = False
- for states in self.states:
- if states[stateIndex] == state:
- res = True
- break
- return res
-
- def isTriggerable(self, obj, wf, noSecurity=False):
- '''Can this transition be triggered on p_obj?'''
- wf = wf.__instance__ # We need the prototypical instance here.
- # Checks that the current state of the object is a start state for this
- # transition.
- objState = obj.State(name=False)
- if self.isSingle():
- if objState != self.states[0]: return False
- else:
- startFound = False
- for startState, stopState in self.states:
- if startState == objState:
- startFound = True
- break
- if not startFound: return False
- # Check that the condition is met, excepted if noSecurity is True.
- if noSecurity: return True
- user = obj.getTool().getUser()
- if isinstance(self.condition, Role):
- # Condition is a role. Transition may be triggered if the user has
- # this role.
- return user.has_role(self.condition.name, obj)
- elif type(self.condition) == types.FunctionType:
- return self.condition(wf, obj.appy())
- elif type(self.condition) in (tuple, list):
- # It is a list of roles and/or functions. Transition may be
- # triggered if user has at least one of those roles and if all
- # functions return True.
- hasRole = None
- for roleOrFunction in self.condition:
- if isinstance(roleOrFunction, basestring):
- if hasRole == None:
- hasRole = False
- if user.has_role(roleOrFunction, obj):
- hasRole = True
- elif type(roleOrFunction) == types.FunctionType:
- if not roleOrFunction(wf, obj.appy()):
- return False
- if hasRole != False:
- return True
-
- def executeAction(self, obj, wf):
- '''Executes the action related to this transition.'''
- msg = ''
- obj = obj.appy()
- wf = wf.__instance__ # We need the prototypical instance here.
- if type(self.action) in (tuple, list):
- # We need to execute a list of actions
- for act in self.action:
- msgPart = act(wf, obj)
- if msgPart: msg += msgPart
- else: # We execute a single action only.
- msgPart = self.action(wf, obj)
- if msgPart: msg += msgPart
- return msg
-
- def trigger(self, transitionName, obj, wf, comment, doAction=True,
- doNotify=True, doHistory=True, doSay=True):
- '''This method triggers this transition on p_obj. The transition is
- supposed to be triggerable (call to self.isTriggerable must have been
- performed before calling this method). If p_doAction is False, the
- action that must normally be executed after the transition has been
- triggered will not be executed. If p_doNotify is False, the
- email notifications that must normally be launched after the
- transition has been triggered will not be launched. If p_doHistory is
- False, there will be no trace from this transition triggering in the
- workflow history. If p_doSay is False, we consider the transition is
- trigger programmatically, and no message is returned to the user.'''
- # Create the workflow_history dict if it does not exist.
- if not hasattr(obj.aq_base, 'workflow_history'):
- from persistent.mapping import PersistentMapping
- obj.workflow_history = PersistentMapping()
- # Create the event list if it does not exist in the dict
- if not obj.workflow_history: obj.workflow_history['appy'] = ()
- # Get the key where object history is stored (this overstructure is
- # only there for backward compatibility reasons)
- key = obj.workflow_history.keys()[0]
- # Identify the target state for this transition
- if self.isSingle():
- targetState = self.states[1]
- targetStateName = targetState.getName(wf)
- else:
- startState = obj.State(name=False)
- for sState, tState in self.states:
- if startState == sState:
- targetState = tState
- targetStateName = targetState.getName(wf)
- break
- # Create the event and add it in the object history
- action = transitionName
- if transitionName == '_init_': action = None
- if not doHistory: comment = '_invisible_'
- obj.addHistoryEvent(action, review_state=targetStateName,
- comments=comment)
- # Reindex the object if required. Not only security-related indexes
- # (Allowed, State) need to be updated here.
- if not obj.isTemporary(): obj.reindex()
- # Execute the related action if needed
- msg = ''
- if doAction and self.action: msg = self.executeAction(obj, wf)
- # Send notifications if needed
- if doNotify and self.notify and obj.getTool(True).mailEnabled:
- sendNotification(obj.appy(), self, transitionName, wf)
- # Return a message to the user if needed
- if not doSay or (transitionName == '_init_'): return
- if not msg: msg = obj.translate('object_saved')
- obj.say(msg)
-
-class Permission:
- '''If you need to define a specific read or write permission of a given
- attribute of an Appy type, you use the specific boolean parameters
- "specificReadPermission" or "specificWritePermission" for this attribute.
- When you want to refer to those specific read or write permissions when
- defining a workflow, for example, you need to use instances of
- "ReadPermission" and "WritePermission", the 2 children classes of this
- class. For example, if you need to refer to write permission of
- attribute "t1" of class A, write: WritePermission("A.t1") or
- WritePermission("x.y.A.t1") if class A is not in the same module as
- where you instantiate the class.
-
- Note that this holds only if you use attributes "specificReadPermission"
- and "specificWritePermission" as booleans. When defining named
- (string) permissions, for referring to it you simply use those strings,
- you do not create instances of ReadPermission or WritePermission.'''
-
- allowedChars = string.digits + string.letters + '_'
-
- def __init__(self, fieldDescriptor):
- self.fieldDescriptor = fieldDescriptor
-
- def getName(self, wf, appName):
- '''Returns the name of this permission.'''
- className, fieldName = self.fieldDescriptor.rsplit('.', 1)
- if className.find('.') == -1:
- # The related class resides in the same module as the workflow
- fullClassName= '%s_%s' % (wf.__module__.replace('.', '_'),className)
- else:
- # className contains the full package name of the class
- fullClassName = className.replace('.', '_')
- # Read or Write ?
- if self.__class__.__name__ == 'ReadPermission': access = 'Read'
- else: access = 'Write'
- return '%s: %s %s %s' % (appName, access, fullClassName, fieldName)
-
-class ReadPermission(Permission): pass
-class WritePermission(Permission): pass
-
-class WorkflowAnonymous:
- '''One-state workflow allowing anyone to consult and Manager to edit.'''
- mgr = 'Manager'
- o = 'Owner'
- active = State({r:(mgr, 'Anonymous', 'Authenticated'), w:(mgr,o),d:(mgr,o)},
- initial=True)
-
-class WorkflowAuthenticated:
- '''One-state workflow allowing authenticated users to consult and Manager
- to edit.'''
- mgr = 'Manager'
- o = 'Owner'
- active = State({r:(mgr, 'Authenticated'), w:(mgr,o), d:(mgr,o)},
- initial=True)
-
-class WorkflowOwner:
- '''One-state workflow allowing only manager and owner to consult and
- edit.'''
- mgr = 'Manager'
- o = 'Owner'
- active = State({r:(mgr, o), w:(mgr, o), d:mgr}, initial=True)
-
# ------------------------------------------------------------------------------
class Model: pass
class Tool(Model):
diff --git a/gen/descriptors.py b/gen/descriptors.py
index dadc509..da86f65 100644
--- a/gen/descriptors.py
+++ b/gen/descriptors.py
@@ -429,16 +429,6 @@ class ToolClassDescriptor(ClassDescriptor):
multiplicity=(1,None), default=('odt',), **pg)
self.addField(fieldName, fieldType)
- def addQueryResultColumns(self, classDescr):
- '''Adds, for class p_classDescr, the attribute in the tool that allows
- to select what default columns will be shown on query results.'''
- className = classDescr.name
- fieldName = 'resultColumnsFor%s' % className
- fieldType = gen.String(multiplicity=(0,None), validator=gen.Selection(
- '_appy_getAllFields*%s' % className), page='userInterface',
- group=classDescr.klass.__name__, default=['title'])
- self.addField(fieldName, fieldType)
-
def addSearchRelatedFields(self, classDescr):
'''Adds, for class p_classDescr, attributes related to the search
functionality for class p_classDescr.'''
diff --git a/gen/generator.py b/gen/generator.py
index c34606a..8e2f238 100644
--- a/gen/generator.py
+++ b/gen/generator.py
@@ -712,12 +712,10 @@ class ZopeGenerator(Generator):
'classDoc': 'Standard Appy class', 'icon': icon})
self.copyFile('Class.pyt', repls, destName='%s.py' % klass.name)
- # Before generating the Tool class, finalize it with query result
- # columns, search-related and import-related fields.
+ # Before generating the Tool class, finalize it with search-related and
+ # import-related fields.
for classDescr in self.getClasses(include='allButTool'):
if not classDescr.isRoot(): continue
- # We must be able to configure query results from the tool.
- self.tool.addQueryResultColumns(classDescr)
# Add the search-related fields.
self.tool.addSearchRelatedFields(classDescr)
importMean = classDescr.getCreateMean('Import')
diff --git a/gen/installer.py b/gen/installer.py
index 8737cdc..7ac2d2d 100644
--- a/gen/installer.py
+++ b/gen/installer.py
@@ -293,8 +293,7 @@ class ZopeInstaller:
title.init('title', None, 'appy')
setattr(wrapperClass, 'title', title)
# Special field "state" must be added for every class
- state = gen.String(validator=gen.Selection('_appy_listStates'),
- show='result')
+ state = gen.String(show='result')
state.init('state', None, 'workflow')
setattr(wrapperClass, 'state', state)
names = self.config.attributes[wrapperClass.__name__[:-8]]
diff --git a/gen/mixins/ToolMixin.py b/gen/mixins/ToolMixin.py
index ef0694c..f63c06e 100644
--- a/gen/mixins/ToolMixin.py
+++ b/gen/mixins/ToolMixin.py
@@ -203,15 +203,6 @@ class ToolMixin(BaseMixin):
cfg = self.getProductConfig()
return [self.getAppyClass(k) for k in cfg.rootClasses]
- def _appy_getAllFields(self, className):
- '''Returns the (translated) names of fields of p_className.'''
- res = []
- for field in self.getAllAppyTypes(className=className):
- res.append((className.name, self.translate(className.labelId)))
- # Add object state
- res.append(('state', self.translate('workflow_state')))
- return res
-
def _appy_getSearchableFields(self, className):
'''Returns the (translated) names of fields that may be searched on
objects of type p_className (=indexed fields).'''
@@ -438,8 +429,8 @@ class ToolMixin(BaseMixin):
if refInfo[0]:
return refInfo[0].getAppyType(refInfo[1]).shownInfo
else:
- toolFieldName = 'resultColumnsFor%s' % className
- return getattr(self.appy(), toolFieldName)
+ k = self.getAppyClass(className)
+ return hasattr(k, 'listColumns') and k.listColumns or ('title',)
def truncateValue(self, value, width=15):
'''Truncates the p_value according to p_width.'''
@@ -469,7 +460,7 @@ class ToolMixin(BaseMixin):
def quote(self, s):
'''Returns the quoted version of p_s.'''
if not isinstance(s, basestring): s = str(s)
- if "'" in s: return '"%s"' % s
+ s = s.replace('\r\n', '').replace('\n', '').replace("'", "\\'")
return "'%s'" % s
def getLayoutType(self):
diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py
index 2e2fbff..a0b07cc 100644
--- a/gen/mixins/__init__.py
+++ b/gen/mixins/__init__.py
@@ -786,7 +786,7 @@ class BaseMixin:
return klass.styles[elem]
return elem
- def getAppyTransitions(self, includeFake=True, includeNotShowable=False):
+ def getTransitions(self, includeFake=True, includeNotShowable=False):
'''This method returns info about transitions that one can trigger from
the user interface.
* if p_includeFake is True, it retrieves transitions that the user
@@ -824,7 +824,7 @@ class BaseMixin:
'confirm': '', 'may_trigger': True}
if transition.confirm:
cLabel = '%s_confirm' % label
- tInfo['confirm'] = self.translate(cLabel, format='js')
+ tInfo['confirm'] = self.translate(cLabel)
if not mayTrigger:
tInfo['may_trigger'] = False
tInfo['reason'] = mayTrigger.msg
@@ -1167,7 +1167,9 @@ class BaseMixin:
'''Gets, according to the workflow, the roles that are currently granted
p_permission on this object.'''
state = self.State(name=False)
- return [role.name for role in state.permissions[permission]]
+ roles = state.permissions[permission]
+ if roles: return [role.name for role in roles]
+ return ()
def appy(self):
'''Returns a wrapper object allowing to manipulate p_self the Appy
@@ -1290,15 +1292,6 @@ class BaseMixin:
if isinstance(showValue, basestring): return layoutType == showValue
return layoutType in showValue
- def _appy_listStates(self):
- '''Lists the possible states for this object.'''
- res = []
- workflow = self.getWorkflow()
- for elem in dir(workflow):
- if getattr(workflow, elem).__class__.__name__ != 'State': continue
- res.append((elem, self.translate(self.getWorkflowLabel(elem))))
- return res
-
getUrlDefaults = {'page':True, 'nav':True}
def getUrl(self, base=None, mode='view', **kwargs):
'''Returns an URL for this object.
@@ -1423,9 +1416,6 @@ class BaseMixin:
if 'html' in format:
if format == 'html_from_text': text = cgi.escape(text)
res = text.replace('\r\n', ' ').replace('\n', ' ')
- elif format == 'js':
- res = text.replace('\r\n', '').replace('\n', '')
- res = res.replace("'", "\\'")
elif format == 'text':
res = text.replace(' ', '\n')
else:
diff --git a/gen/model.py b/gen/model.py
index a46a0dd..608e65e 100644
--- a/gen/model.py
+++ b/gen/model.py
@@ -214,8 +214,8 @@ setattr(Page, Page.pages.back.attribute, Page.pages.back)
# The Tool class ---------------------------------------------------------------
# Prefixes of the fields generated on the Tool.
-toolFieldPrefixes = ('podTemplate', 'formats', 'resultColumns',
- 'numberOfSearchColumns', 'searchFields')
+toolFieldPrefixes = ('podTemplate', 'formats', 'numberOfSearchColumns',
+ 'searchFields')
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
'appyVersion', 'dateFormat', 'hourFormat', 'users',
'connectedUsers', 'groups', 'translations',
diff --git a/gen/po.py b/gen/po.py
index ad7d663..503e782 100644
--- a/gen/po.py
+++ b/gen/po.py
@@ -221,7 +221,6 @@ CONFIG = "Configuration panel for product '%s'"
# to MSG_).
MSG_podTemplate = "POD template for field '%s'"
MSG_formats = "Output format(s) for field '%s'"
-MSG_resultColumns = "Columns to display while showing query results"
MSG_numberOfSearchColumns = "Number of search columns"
MSG_searchFields = "Search fields"
POD_ASKACTION = 'Trigger related action'
diff --git a/gen/wrappers/ToolWrapper.py b/gen/wrappers/ToolWrapper.py
index 26ad711..83fb911 100644
--- a/gen/wrappers/ToolWrapper.py
+++ b/gen/wrappers/ToolWrapper.py
@@ -335,18 +335,28 @@ class ToolWrapper(AbstractWrapper):
style=":showSubTitles and 'display:inline' or 'display:none'"
name="subTitle">::zobj.getSubTitle()
-
-
+
@@ -703,12 +713,10 @@ class ToolWrapper(AbstractWrapper):
return self.o.getAppyClass(zopeName)
def getAttributeName(self, attributeType, klass, attrName=None):
- '''Some names of Tool attributes are not easy to guess. For example,
- the attribute that stores the names of the columns to display in
- query results for class A that is in package x.y is
- "tool.resultColumnsForx_y_A". This method generates the attribute
- name based on p_attributeType, a p_klass from the application, and a
- p_attrName (given only if needed). p_attributeType may be:
+ '''Some names of Tool attributes are not easy to guess. This method
+ generates the attribute name based on p_attributeType, a p_klass from
+ the application, and a p_attrName (given only if needed).
+ p_attributeType may be:
"podTemplate"
Stores the pod template for p_attrName.
@@ -717,10 +725,6 @@ class ToolWrapper(AbstractWrapper):
Stores the output format(s) of a given pod template for
p_attrName.
- "resultColumns"
- Stores the list of columns that must be shown when displaying
- instances of a given root p_klass.
-
"numberOfSearchColumns"
Determines in how many columns the search screen for p_klass
is rendered.
diff --git a/gen/wrappers/__init__.py b/gen/wrappers/__init__.py
index bc6582b..178b377 100644
--- a/gen/wrappers/__init__.py
+++ b/gen/wrappers/__init__.py
@@ -354,7 +354,7 @@ class AbstractWrapper(object):
# Displays an object's transitions(s).
pxTransitions = Px('''
-