From 68ed9d878bd9c1024b7cc9fee237e8dea156abd3 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 18 Jul 2013 13:26:25 +0200 Subject: [PATCH] provide 'actors' and 'condition' attributes for more control on actions/transitions --- stateful/README.txt | 44 +++++++++++++++++++++++++++++++++++++++--- stateful/base.py | 3 +++ stateful/definition.py | 17 ++++++++++++++-- stateful/interfaces.py | 17 ++++++++++++++++ 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/stateful/README.txt b/stateful/README.txt index fd7435c..f3fefca 100644 --- a/stateful/README.txt +++ b/stateful/README.txt @@ -2,8 +2,6 @@ Stateful Objects ================ - ($Id$) - >>> from cybertools.stateful.definition import StatesDefinition >>> from cybertools.stateful.definition import State, Transition >>> from cybertools.stateful.definition import registerStatesDefinition @@ -13,7 +11,9 @@ We start with a simple demonstration class that provides stateful behaviour directly. >>> class Demo(Stateful): - ... pass + ... currentActors = None + ... def getActors(self): + ... return self.currentActors >>> demo = Demo() @@ -57,6 +57,43 @@ of the current state we get an error. >>> demo.getState() 'draft' +Check condition +--------------- + + >>> def checkIfEmpty(obj): + ... return getattr(obj, 'empty', True) + + >>> removeAction = demo.getStatesDefinition().transitions.remove + >>> removeAction.condition = checkIfEmpty + >>> removeAction in demo.getAvailableTransitions() + True + + >>> demo.empty = False + >>> removeAction in demo.getAvailableTransitions() + False + +Check actors +------------ + + >>> removeAction.actors = ['master'] + + >>> demo.getActors() + + >>> demo.empty = True + >>> removeAction in demo.getAvailableTransitionsForUser() + True + + >>> demo.currentActors = ['dummy'] + >>> demo.getActors() + ['dummy'] + + >>> removeAction in demo.getAvailableTransitionsForUser() + False + + >>> demo.currentActors = ['master'] + >>> removeAction in demo.getAvailableTransitionsForUser() + True + Stateful Adapters ================= @@ -94,3 +131,4 @@ back the state that is stored with the object. >>> statefulDemo = IStateful(demo) >>> statefulDemo.getState() 'finished' + diff --git a/stateful/base.py b/stateful/base.py index 9f41686..39d088c 100644 --- a/stateful/base.py +++ b/stateful/base.py @@ -82,6 +82,9 @@ class Stateful(object): def getStatesDefinition(self): return statesDefinitions.get(self.statesDefinition, None) + def getActors(self): + return None + def notify(self, transition, previousState): """ To be implemented by subclass. """ diff --git a/stateful/definition.py b/stateful/definition.py index c2d810a..b8b8131 100644 --- a/stateful/definition.py +++ b/stateful/definition.py @@ -55,6 +55,8 @@ class Action(object): allowed = True permission = None roles = [] + actors = [] + condition = None doBefore = [] schema = None @@ -69,8 +71,6 @@ class Transition(Action): implements(ITransition) - actors = None - def __init__(self, name, title, targetState, **kw): super(Transition, self).__init__(name, title, **kw) self.targetState = targetState @@ -123,12 +123,25 @@ class StatesDefinition(object): def isAllowed(self, action, obj): if not action.allowed: return False + if action.condition and not action.condition(obj): + return False + if not self.checkActors(action.actors, obj): + return False if not self.checkRoles(action.roles, obj): return False if not self.checkPermission(action.permission, obj): return False return True + def checkActors(self, actors, obj): + stfActors = obj.getActors() + if stfActors is None: + return True + for actor in actors: + if actor in stfActors: + return True + return False + def checkRoles(self, roles, obj): return True diff --git a/stateful/interfaces.py b/stateful/interfaces.py index ff3b752..e312f50 100644 --- a/stateful/interfaces.py +++ b/stateful/interfaces.py @@ -50,6 +50,12 @@ class IAction(Interface): '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.') + actors = Attribute('A collection of names of actors or groups that should be ' + 'able to execute this action; no check when empty. ' + 'See the IStateful.getActors().') + condition = Attribute('A boolean function with a stateful object as ' + 'parameter. The action is only allowed if return value ' + 'is True. No check when condition is None.') schema = Attribute('An optional schema (a sequence of field specifications) ' 'that provides information on fields to be shown in a ' 'form used for executing the action.') @@ -97,6 +103,17 @@ class IStateful(Interface): for the current state. """ + def getActors(): + """ Return a collection of names of actors or groups that will be + used for checking if a certain transition is allowed. May be + None in which case not checking should be applied. + """ + + def notify(transition, previousState): + """ This method will be called upon completion of a transition. + """ + + request = Attribute('Optional publication request.')