[gen] Replaced database fields tool.resultColumnsFor[class] by static attributes class.listColumns. [gen] Bugfixes and removed unused code.

This commit is contained in:
Gaetan Delannay 2013-09-18 12:06:07 +02:00
parent 204d7644b2
commit 809a553cf4
14 changed files with 397 additions and 392 deletions

View file

@ -401,11 +401,7 @@ class Calendar(Field):
res = Object(eventTypes=eventTypes, message=message)
if forBrowser:
res.eventTypes = ','.join(res.eventTypes)
if not res.message:
res.message = ''
else:
res.message = obj.formatText(res.message, format='js')
return res.__dict__
if not res.message: res.message = ''
return res
def getEventsAt(self, obj, date):

View file

@ -49,7 +49,7 @@ class Date(Field):
<select name=":'%s_year' % name" id=":'%s_year' % name">
<option value="">-</option>
<option for="year in years" value=":year"
selected=":field.isSelected(zobj, name, 'year', year, \
selected=":field.isSelected(zobj, 'year', year, \
rawValue)">:year</option>
</select>
@ -57,7 +57,7 @@ class Date(Field):
<x if="field.calendar">
<input type="hidden" id=":name" name=":name"/>
<img id=":'%s_img' % name" src=":url('calendar.gif')"/>
<script type="text/javascript">:field.getJsInit(name, years)</script>
<script type="text/javascript">::field.getJsInit(name, years)</script>
</x>
<!-- Hour and minutes -->
@ -112,7 +112,7 @@ class Date(Field):
<x if="field.calendar">
<input type="hidden" id=":fromName" name=":fromName"/>
<img id=":'%s_img' % fromName" src=":url('calendar.gif')"/>
<script type="text/javascript">:field.getJsInit(fromName, years)
<script type="text/javascript">::field.getJsInit(fromName, years)
</script>
</x>
</td>
@ -145,7 +145,7 @@ class Date(Field):
<x if="widget.calendar">
<input type="hidden" id=":toName" name=":toName"/>
<img id=":'%s_img' % toName" src=":url('calendar.gif')"/>
<script type="text/javascript">:field.getJsInit(toName, years)">
<script type="text/javascript">::field.getJsInit(toName, years)">
</script>
</x>
</td>

View file

@ -31,7 +31,7 @@ class Phase:
if="not singlePhase and not singlePage">::_(label)</div>
<!-- The page(s) within the phase -->
<x for="aPage in phase.pages">
<x for="aPage in phase.pages" var2="aPageInfo=phase.pagesInfo[aPage]">
<!-- First line: page name and icons -->
<div if="not (singlePhase and singlePage)"
class=":aPage==page and 'portletCurrent portletPage' or \
@ -39,7 +39,8 @@ class Phase:
<a href=":zobj.getUrl(page=aPage)">::_('%s_page_%s' % \
(zobj.meta_type, aPage))</a>
<x var="locked=zobj.isLocked(user, aPage);
editable=mayEdit and phase.pagesInfo[aPage].showOnEdit">
editable=mayEdit and aPageInfo.showOnEdit and \
aPageInfo.showEdit">
<a if="editable and not locked"
href=":zobj.getUrl(mode='edit', page=aPage)">
<img src=":url('edit')" title=":_('object_edit')"/></a>
@ -57,7 +58,7 @@ class Phase:
</x>
</div>
<!-- Next lines: links -->
<x var="links=phase.pagesInfo[aPage].links" if="links">
<x var="links=aPageInfo.links" if="links">
<div for="link in links"><a href=":link.url">:link.title</a></div>
</x>
</x>

329
fields/workflow.py Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
# ------------------------------------------------------------------------------
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)
# ------------------------------------------------------------------------------

View file

@ -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 <http://www.gnu.org/licenses/>.
# ------------------------------------------------------------------------------
# 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):

View file

@ -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.'''

View file

@ -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')

View file

@ -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]]

View file

@ -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 '&quot;%s&quot;' % s
s = s.replace('\r\n', '').replace('\n', '').replace("'", "\\'")
return "'%s'" % s
def getLayoutType(self):

View file

@ -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', '<br/>').replace('\n', '<br/>')
elif format == 'js':
res = text.replace('\r\n', '').replace('\n', '')
res = res.replace("'", "\\'")
elif format == 'text':
res = text.replace('<br/>', '\n')
else:

View file

@ -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',

View file

@ -221,7 +221,6 @@ CONFIG = "Configuration panel for product '%s'"
# to MSG_<attributePrefix>).
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'

View file

@ -335,18 +335,28 @@ class ToolWrapper(AbstractWrapper):
style=":showSubTitles and 'display:inline' or 'display:none'"
name="subTitle">::zobj.getSubTitle()</span>
<!-- Actions: edit, delete -->
<div if="zobj.mayAct()">
<a if="zobj.mayEdit()"
var2="navInfo='search.%s.%s.%d.%d' % \
<!-- Actions -->
<table class="noStyle" if="zobj.mayAct()">
<tr>
<!-- Workflow transitions -->
<td if="zobj.showTransitions('result')"
var2="targetObj=zobj">:targetObj.appy().pxTransitions</td>
<!-- Edit -->
<td if="zobj.mayEdit()">
<a var="navInfo='search.%s.%s.%d.%d' % \
(className, searchName, loop.zobj.nb+1+startNumber, totalNumber)"
href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \
nav=navInfo)">
<img src=":url('edit')" title=":_('object_edit')"/></a>
<img if="zobj.mayDelete()" class="clickable" src=":url('delete')"
title=":_('object_delete')"
onClick=":'onDeleteObject(%s)' % q(zobj.UID())"/>
</div>
href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \
nav=navInfo)">
<img src=":url('edit')" title=":_('object_edit')"/></a>
</td>
<td>
<!-- Delete -->
<img if="zobj.mayDelete()" class="clickable" src=":url('delete')"
title=":_('object_delete')"
onClick=":'onDeleteObject(%s)' % q(zobj.UID())"/>
</td>
</tr>
</table>
</x>
<!-- Any other field -->
<x if="field.name != 'title'">
@ -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.

View file

@ -354,7 +354,7 @@ class AbstractWrapper(object):
# Displays an object's transitions(s).
pxTransitions = Px('''
<form var="transitions=targetObj.getAppyTransitions()" if="transitions"
<form var="transitions=targetObj.getTransitions()" if="transitions"
var2="formId='trigger_%s' % targetObj.UID()" method="post"
id=":formId" action=":targetObj.absolute_url() + '/do'">
<input type="hidden" name="action" value="Trigger"/>
@ -480,7 +480,8 @@ class AbstractWrapper(object):
<td if="not isEdit"
var2="locked=zobj.isLocked(user, page);
editable=pageInfo.showOnEdit and zobj.mayEdit()">
editable=pageInfo.showOnEdit and pageInfo.showEdit and \
zobj.mayEdit()">
<!-- Edit -->
<input type="button" class="button" if="editable and not locked"