add state-dependent control of actions and a doBefore handler for transitions

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3240 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2009-02-19 09:36:05 +00:00
parent 1fa202c5c2
commit 35f9224c25
3 changed files with 89 additions and 23 deletions

View file

@ -142,5 +142,3 @@ class TransitionEvent(ObjectEvent):
self.transition = transition
self.previousState = previousState
self.request = request

View file

@ -25,7 +25,7 @@ $Id$
from zope.interface import implements
from cybertools.util.jeep import Jeep
from cybertools.stateful.interfaces import IState, ITransition
from cybertools.stateful.interfaces import IState, IAction, ITransition
from cybertools.stateful.interfaces import IStatesDefinition
@ -33,7 +33,7 @@ class State(object):
implements(IState)
security = lambda context: None
setSecurity = lambda self, context: None
icon = None
color = 'blue'
@ -41,20 +41,37 @@ class State(object):
self.name = self.__name__ = name
self.title = title
self.transitions = transitions
self.actions = Jeep(kw.pop('actions', []))
for k, v in kw.items():
setattr(self, k, v)
class Transition(object):
class Action(object):
implements(IAction)
allowed = True
permission = None
roles = []
def __init__(self, name, title=None, **kw):
self.name = self.__name__ = name
self.title = title
for k, v in kw.items():
setattr(self, k, v)
@staticmethod
def doBefore(context):
return None
class Transition(Action):
implements(ITransition)
def __init__(self, name, title, targetState, **kw):
self.name = self.__name__ = name
self.title = title
super(Transition, self).__init__(name, title, **kw)
self.targetState = targetState
for k, v in kw.items():
setattr(self, k, v)
class StatesDefinition(object):
@ -81,14 +98,36 @@ class StatesDefinition(object):
def doTransitionFor(self, obj, transition):
if transition not in self.transitions:
raise ValueError('Transition %s is not available.' % transition)
if transition not in [t.name for t in self.getAvailableTransitionsFor(obj)]:
trans = self.transitions[transition]
if not self.isAllowed(trans, obj):
raise ValueError('Transition %s is not allowed.' % transition)
if trans not in self.getAvailableTransitionsFor(obj):
raise ValueError("Transition '%s' is not reachable from state '%s'."
% (transition, obj.getState()))
obj.state = self.transitions[transition].targetState
trans.doBefore(obj)
obj.state = trans.targetState
obj.getStateObject().setSecurity(obj)
def getAvailableTransitionsFor(self, obj):
state = obj.getState()
return [self.transitions[t] for t in self.states[state].transitions]
return [self.transitions[t]
for t in self.states[state].transitions
if self.isAllowed(self.transitions[t], obj)]
def isAllowed(self, action, obj):
if not action.allowed:
return False
if not self.checkRoles(action.roles, obj):
return False
if not self.checkPermission(action.permission, obj):
return False
return True
def checkRoles(self, roles, obj):
return True
def checkPermission(self, permission, obj):
return True
# dummy default states definition
@ -101,6 +140,7 @@ defaultSD = StatesDefinition('default',
# states definitions registry
# TODO: use a utility!!!
statesDefinitions = dict()

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
# Copyright (c) 2009 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -33,18 +33,31 @@ class IState(Interface):
name = Attribute('The name or identifier of the state')
title = Attribute('A user-readable name or title of the state')
transitions = Attribute('A sequence of strings naming the transitions '
'that can be executed from this state')
security = Attribute('A callable setting the security settings for '
'an object in this state when executed.')
transitions = Attribute('A collection of strings naming the transitions '
'that can be executed from this state.')
actions = Attribute('A mapping with actions that may be executed in '
'this state.')
setSecurity = Attribute('A callable (argument: stateful object) '
'for setting the security settings on the object.')
class ITransition(Interface):
class IAction(Interface):
name = Attribute('The name or identifier of the action.')
title = Attribute('A user-readable name or title of the action.')
allowed = Attribute('A boolean; if False the action may not be executed.')
doBefore = Attribute('A callable (argument: stateful object) to be executed '
'before this action.')
roles = Attribute('A collection of names of the roles that are allowed '
'to execute this action; no check when empty.')
permission = Attribute('The name of a permission that is needed for '
'executing this action; no check when empty.')
class ITransition(IAction):
name = Attribute('The name or identifier of the transition')
title = Attribute('A user-readable name or title of the transition')
targetState = Attribute('A string naming the state that will be the '
'result of executing this transition')
'result of executing this transition.')
class IStateful(Interface):
@ -119,9 +132,24 @@ class IStatesDefinition(Interface):
""" Return the transitions available for this object in its current state.
"""
def isAllowed(action, object):
""" Return True if the action (an IAction provider) is allowed on
the object given for the current user.
"""
def checkRoles(roles, object):
""" Return True if the current user provides one of the roles given
on the object given.
"""
def checkRoles(permission, object):
""" Return True if the current user has the permission given
on the object given.
"""
class IStatefulIndexInfo(Interface):
""" Provide a list of tokens to be used for index the states
""" Provide a list of tokens to be used for indexing the states
of an object in the catalog.
"""
@ -133,7 +161,7 @@ class IStatefulIndexInfo(Interface):
class ITransitionEvent(IObjectEvent):
""" Fires when the state of an object is changed.
""" Fires when the state of an object has been changed.
"""
transition = Attribute('The transition.')