diff --git a/composer/schema/field.py b/composer/schema/field.py index 7457dc3..84e6b4b 100644 --- a/composer/schema/field.py +++ b/composer/schema/field.py @@ -59,11 +59,13 @@ class Field(Component): def name(self): return self.__name__ - @property - def defaultValue(self): + def getDefaultValue(self): if callable(self.default): return self.default() return self.default + def setDefaultValue(self, value): + self.default = value + defaultValue = property(getDefaultValue, setDefaultValue) @property def fieldRenderer(self): diff --git a/organize/browser/service.py b/organize/browser/service.py index e5c3b16..3bbb23c 100644 --- a/organize/browser/service.py +++ b/organize/browser/service.py @@ -32,6 +32,7 @@ from cybertools.organize.interfaces import serviceCategories from cybertools.composer.interfaces import IInstance from cybertools.composer.schema.browser.common import BaseView as SchemaBaseView from cybertools.composer.schema.interfaces import IClientFactory +from cybertools.stateful.interfaces import IStateful from cybertools.util.format import formatDate @@ -97,6 +98,7 @@ class ServiceManagerView(BaseView): first = tpl return first + #@Lazy - Zope 2.9 compatibility def registrationUrl(self): tpl = self.getRegistrationTemplate() return self.getUrlForObject(tpl) @@ -170,7 +172,8 @@ class CheckoutView(ServiceManagerView): return True # TODO: error, redirect to overview regs = IClientRegistrations(client).getRegistrations() for reg in regs: - pass # set state to submitted, + stateful = IStateful(reg) + stateful.doTransition(('submit', 'change')) # send mail # find thank you message and redirect to it params = '?message=thankyou&id=' + self.clientName @@ -190,6 +193,7 @@ class ServiceView(BaseView): man = context.getManager() return ServiceManagerView(man, self.request).getRegistrationTemplate() + #@Lazy - Zope 2.9 compatibility def registrationUrl(self): tpl = self.getRegistrationTemplate() return self.getUrlForObject(tpl) @@ -213,6 +217,12 @@ class ServiceView(BaseView): instance = IInstance(client) return instance.applyTemplate() + def getRegistrationInfo(self, reg): + registration = self.getRegistrations()[reg] + state = IStateful(registration).getStateObject() + return dict(number=registration.number, + state=state.name, stateTitle=state.title) + def update(self): newClient = False nextUrl = None diff --git a/organize/service.py b/organize/service.py index 260978a..0bf8aaf 100644 --- a/organize/service.py +++ b/organize/service.py @@ -33,6 +33,7 @@ from zope.interface import implements, Interface from cybertools.composer.interfaces import IInstance from cybertools.composer.rule.base import RuleManager, EventType from cybertools.composer.schema.interfaces import IClientManager, IClient +from cybertools.stateful.base import StatefulAdapter from cybertools.stateful.definition import registerStatesDefinition from cybertools.stateful.definition import StatesDefinition from cybertools.stateful.definition import State, Transition @@ -97,11 +98,30 @@ class ServiceManager(RuleManager): return self.rules +class Registration(object): + + implements(IRegistration) + + number = 1 + + def __init__(self, client, service, number=1): + self.client = client + self.service = service + self.timeStamp = int(time()) + self.number = number + + +class PersistentRegistration(Registration, Persistent): + + pass + + class Service(object): implements(IService) registrationsFactory = OOBTree + registrationFactory = PersistentRegistration manager = None category = None @@ -150,9 +170,9 @@ class Service(object): reg = self.registrations[clientName] if number != reg.number: reg.number = number - self.registrations[clientName] = reg # persistence hack + #self.registrations[clientName] = reg # persistence hack return reg - reg = Registration(client, self, number) + reg = self.registrationFactory(client, self, number) self.registrations[clientName] = reg return reg #if self.availableCapacity: @@ -184,20 +204,7 @@ class ScheduledService(Service): return getattr(self.getManager(), 'end', None) -# registration - -class Registration(object): - - implements(IRegistration) - - number = 1 - - def __init__(self, client, service, number=1): - self.client = client - self.service = service - self.timeStamp = int(time()) - self.number = number - +# registration stuff class RegistrationTemplate(object): @@ -253,7 +260,6 @@ class ClientRegistrations(object): service.unregister(self.context) def getRegistrations(self): - # TODO: restrict to services on this template regs = getattr(self.context, self.registrationsAttributeName, []) if self.template is not None: svcs = self.template.getServices().values() @@ -261,19 +267,26 @@ class ClientRegistrations(object): return regs -# registration states definition +# registration states + +registrationStates = 'organize.service.registration' registerStatesDefinition( - StatesDefinition('organize.service.registration', + StatesDefinition(registrationStates, State('temporary', 'temporary', ('submit', 'cancel',)), - State('submitted', 'submitted', ('retract', 'setwaiting', 'confirm', 'reject',)), + State('submitted', 'submitted', + ('change', 'retract', 'setwaiting', 'confirm', 'reject',)), State('cancelled', 'cancelled', ('submit',)), State('retracted', 'retracted', ('submit',)), - State('waiting', 'waiting', ('retract', 'confirm', 'reject',)), - State('confirmed', 'confirmed', ('retract', 'reject',)), - State('rejected', 'rejected', ('retract', 'setwaiting', 'confirm',)), + State('waiting', 'waiting', + ('change', 'retract', 'confirm', 'reject',)), + State('confirmed', 'confirmed', + ('change', 'retract', 'reject',)), + State('rejected', 'rejected', + ('change', 'retract', 'setwaiting', 'confirm',)), Transition('cancel', 'Cancel registration', 'cancelled'), Transition('submit', 'Submit registration', 'submitted'), + Transition('change', 'Change registration', 'submitted'), Transition('retract', 'Retract registration', 'retracted'), Transition('setwaiting', 'Set on waiting list', 'waiting'), Transition('confirm', 'Confirm registration', 'confirmed'), @@ -282,7 +295,14 @@ registerStatesDefinition( )) -# event types +class StatefulRegistration(StatefulAdapter): + + component.adapts(IRegistration) + + statesDefinition = registrationStates + + +# event types for rule-based processing eventTypes = Jeep(( EventType('service.checkout'), @@ -297,3 +317,11 @@ def clientRemoved(obj, event): regs = IClientRegistrations(obj) for r in regs.getRegistrations(): r.service.unregister(obj) + +def serviceRemoved(obj, event): + """ Handle removal of a service. + """ + for r in obj.registrations.values(): + regs = IClientRegistrations(r.client) + regs.unregister([obj]) + diff --git a/stateful/base.py b/stateful/base.py index c71a34a..f31e3ba 100644 --- a/stateful/base.py +++ b/stateful/base.py @@ -47,11 +47,18 @@ class Stateful(object): state = self.getState() return self.getStatesDefinition().states[state] - def doTransition(self, transition): - """ execute transition. - """ + def doTransition(self, transition, historyInfo=None): sd = self.getStatesDefinition() - sd.doTransitionFor(self, transition) + if isinstance(transition, basestring): + sd.doTransitionFor(self, transition) + return + available = [t.name for t in sd.getAvailableTransitionsFor(self)] + for tr in transition: + if tr in available: + sd.doTransitionFor(self, tr) + return + raise ValueError("None of the transitions '%s' is available for state '%s'." + % (repr(transition), self.getState())) def getAvailableTransitions(self): sd = self.getStatesDefinition() diff --git a/stateful/interfaces.py b/stateful/interfaces.py index 79102ae..8a1d028 100644 --- a/stateful/interfaces.py +++ b/stateful/interfaces.py @@ -50,18 +50,42 @@ class IStateful(Interface): """ def getStateObject(): - """ Return the state (an IState implementation) of the object. + """ Return the state (an IState implementation) of the context object. """ - def doTransition(transition): + def doTransition(transition, historyInfo=None): """ Execute a transition; the transition is specified by its name. + + The ``transition`` argument may be an iterable; in this case + its elements will be checked against the available transitions + and the first one that's available will be executed. + + The ``historyInfo`` argument is an arbitrary object that will be + used for recording the transition execution in the history + (only if the context object is adaptable to IHistorizable). """ def getAvailableTransitions(): """ Return the transitions for this object that are available in - the current state. The implementation of the returned transition - objects is not specified, they may be action dictionaries or - special Transition objects. + the current state. The returned transition objects should + provide the ITransition interface. + """ + + +class IHistorizable(Interface): + """ An object that may record history information, e.g. when + performing a state transition. + """ + + def record(info): + """ Record the information given (typically a mapping) with the + object. + """ + + def recordTransition(stateFrom, stateTo, transition, historyInfo=None): + """ Record the state transition characterized by the arguments + (names of the states and the transition), optionally + supplemented by the additional history information given. """