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.transition = transition
self.previousState = previousState self.previousState = previousState
self.request = request self.request = request

View file

@ -25,7 +25,7 @@ $Id$
from zope.interface import implements from zope.interface import implements
from cybertools.util.jeep import Jeep 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 from cybertools.stateful.interfaces import IStatesDefinition
@ -33,7 +33,7 @@ class State(object):
implements(IState) implements(IState)
security = lambda context: None setSecurity = lambda self, context: None
icon = None icon = None
color = 'blue' color = 'blue'
@ -41,20 +41,37 @@ class State(object):
self.name = self.__name__ = name self.name = self.__name__ = name
self.title = title self.title = title
self.transitions = transitions self.transitions = transitions
self.actions = Jeep(kw.pop('actions', []))
for k, v in kw.items(): for k, v in kw.items():
setattr(self, k, v) 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) implements(ITransition)
def __init__(self, name, title, targetState, **kw): def __init__(self, name, title, targetState, **kw):
self.name = self.__name__ = name super(Transition, self).__init__(name, title, **kw)
self.title = title
self.targetState = targetState self.targetState = targetState
for k, v in kw.items():
setattr(self, k, v)
class StatesDefinition(object): class StatesDefinition(object):
@ -81,14 +98,36 @@ class StatesDefinition(object):
def doTransitionFor(self, obj, transition): def doTransitionFor(self, obj, transition):
if transition not in self.transitions: if transition not in self.transitions:
raise ValueError('Transition %s is not available.' % transition) 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'." raise ValueError("Transition '%s' is not reachable from state '%s'."
% (transition, obj.getState())) % (transition, obj.getState()))
obj.state = self.transitions[transition].targetState trans.doBefore(obj)
obj.state = trans.targetState
obj.getStateObject().setSecurity(obj)
def getAvailableTransitionsFor(self, obj): def getAvailableTransitionsFor(self, obj):
state = obj.getState() 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 # dummy default states definition
@ -101,6 +140,7 @@ defaultSD = StatesDefinition('default',
# states definitions registry # states definitions registry
# TODO: use a utility!!!
statesDefinitions = dict() 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 # 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 # 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') name = Attribute('The name or identifier of the state')
title = Attribute('A user-readable name or title of the state') title = Attribute('A user-readable name or title of the state')
transitions = Attribute('A sequence of strings naming the transitions ' transitions = Attribute('A collection of strings naming the transitions '
'that can be executed from this state') 'that can be executed from this state.')
security = Attribute('A callable setting the security settings for ' actions = Attribute('A mapping with actions that may be executed in '
'an object in this state when executed.') '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 ' targetState = Attribute('A string naming the state that will be the '
'result of executing this transition') 'result of executing this transition.')
class IStateful(Interface): class IStateful(Interface):
@ -119,9 +132,24 @@ class IStatesDefinition(Interface):
""" Return the transitions available for this object in its current state. """ 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): 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. of an object in the catalog.
""" """
@ -133,7 +161,7 @@ class IStatefulIndexInfo(Interface):
class ITransitionEvent(IObjectEvent): 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.') transition = Attribute('The transition.')