work in progress: work item (task) management
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3080 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
ee49d66e0d
commit
8572156c11
4 changed files with 143 additions and 13 deletions
|
@ -91,5 +91,39 @@ We are now ready to set up the tracking storage.
|
|||
The work management only deals with the IDs or names of tasks and persons,
|
||||
so we do not have to set up real objects.
|
||||
|
||||
>>> workItems.add('001', 'john')
|
||||
<WorkItem ['001', 1, 'john', '2008-12-22 12:07', 'created']: {}>
|
||||
>>> wi01 = workItems.add('001', 'john')
|
||||
>>> wi01
|
||||
<WorkItem ['001', 1, 'john', '...', 'created']:
|
||||
{'created': ..., 'creator': 'john'}>
|
||||
|
||||
Properties that have not been set explicitly default to None; properties
|
||||
not specified in the IWorkItem interface will lead to an AttributeError.
|
||||
|
||||
>>> wi01.description is None
|
||||
True
|
||||
>>> wi01.something
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: something
|
||||
|
||||
Certain (not all) properties may be set after creation.
|
||||
|
||||
>>> wi01.setInitData(planStart=1229955772, planDuration=600, party='jim')
|
||||
>>> wi01
|
||||
<WorkItem ['001', 1, 'jim', '2008-12-22 14:22', 'created']:
|
||||
{'created': ..., 'planEnd': 1229956372, 'planDuration': 600,
|
||||
'planStart': 1229955772, 'creator': 'john', 'planEffort': 600}>
|
||||
|
||||
Change work item states
|
||||
-----------------------
|
||||
|
||||
>>> wi01.assign()
|
||||
>>> wi01.state
|
||||
'assigned'
|
||||
|
||||
>>> wi01.startWork(start=1229958000)
|
||||
>>> wi01
|
||||
<WorkItem ['001', 1, 'jim', '2008-12-22 15:00', 'running']:
|
||||
{'created': ..., 'planEnd': 1229956372, 'start': 1229958000,
|
||||
'assigned': ..., 'planDuration': 600, 'planStart': 1229955772,
|
||||
'creator': 'john', 'planEffort': 600}>
|
||||
|
|
|
@ -449,12 +449,33 @@ class IWorkItem(Interface):
|
|||
effort = Attribute('How much effort (time units) it took to finish the work.')
|
||||
# work item handling
|
||||
creator = Attribute('The party that has set up the work item.')
|
||||
created = Attribute('The timeStamp of the initial creation of the work item.')
|
||||
assigned = Attribute('The timeStamp of the assignment of the work item.')
|
||||
predecessor = Attribute('Optional: a work item this work item was created from.')
|
||||
continuation = Attribute('Optional: a work item that was created from this one '
|
||||
'to continue the work.')
|
||||
newTask = Attribute('Optional: a new task that has been created based '
|
||||
'on this work item.')
|
||||
|
||||
def setInitData(**kw):
|
||||
""" Set initial work item data (if allowed by the current state);
|
||||
values not given will be derived if appropriate.
|
||||
"""
|
||||
|
||||
def assign(party):
|
||||
""" Assign the work item to the party given.
|
||||
"""
|
||||
|
||||
def startWork(**kw):
|
||||
""" Start working on the work item; properties may be given
|
||||
as keyword arguments.
|
||||
"""
|
||||
|
||||
def stopWork(transition='finish', **kw):
|
||||
""" Finish working on the work item; properties may be given
|
||||
as keyword arguments.
|
||||
"""
|
||||
|
||||
|
||||
class IWorkItems(Interface):
|
||||
""" A collection (manager, container) of work items.
|
||||
|
|
|
@ -25,13 +25,14 @@ $Id$
|
|||
from zope import component
|
||||
from zope.component import adapts
|
||||
from zope.interface import implementer, implements
|
||||
from zope.traversing.api import getParent
|
||||
|
||||
from cybertools.organize.interfaces import IWorkItem, IWorkItems
|
||||
from cybertools.stateful.base import Stateful
|
||||
from cybertools.stateful.definition import StatesDefinition
|
||||
from cybertools.stateful.definition import State, Transition
|
||||
from cybertools.stateful.interfaces import IStatesDefinition
|
||||
from cybertools.tracking.btree import Track
|
||||
from cybertools.tracking.btree import Track, getTimeStamp
|
||||
from cybertools.tracking.interfaces import ITrackingStorage
|
||||
|
||||
|
||||
|
@ -62,8 +63,6 @@ class WorkItem(Stateful):
|
|||
def getStatesDefinition(self):
|
||||
return component.getUtility(IStatesDefinition, name=self.statesDefinition)
|
||||
|
||||
# work item attributes (except state that is provided by stateful
|
||||
|
||||
|
||||
class WorkItemTrack(WorkItem, Track):
|
||||
""" A work item that may be stored as a track in a tracking storage.
|
||||
|
@ -73,21 +72,93 @@ class WorkItemTrack(WorkItem, Track):
|
|||
index_attributes = metadata_attributes
|
||||
typeName = 'WorkItem'
|
||||
|
||||
initAttributes = set(['description', 'predecessor',
|
||||
initAttributes = set(['party', 'description', 'predecessor',
|
||||
'planStart', 'planEnd', 'planDuration', 'planEffort'])
|
||||
|
||||
def __init__(self, taskId, runId, userName, data={}):
|
||||
for k in data:
|
||||
if k not in initAttributes:
|
||||
raise ValueError("Illegal initial attribute: '%s'." % k)
|
||||
super(WorkItemTrack, self).__init__(taskId, runId, userName, data)
|
||||
super(WorkItemTrack, self).__init__(taskId, runId, userName)
|
||||
self.state = self.getState() # make initial state persistent
|
||||
self.data['creator'] = userName
|
||||
self.data['created'] = self.timeStamp
|
||||
self.setInitData(**data)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
value = self.data.get(attr, _not_found)
|
||||
if value is _not_found:
|
||||
if attr not in IWorkItem:
|
||||
raise AttributeError(attr)
|
||||
return value
|
||||
return self.data.get(attr, None)
|
||||
|
||||
@property
|
||||
def party(self):
|
||||
return self.userName
|
||||
|
||||
def setInitData(self, **kw):
|
||||
for k in kw:
|
||||
if k not in self.initAttributes:
|
||||
raise ValueError("Illegal initial attribute: '%s'." % k)
|
||||
party = kw.pop('party', None)
|
||||
if party is not None:
|
||||
if self.state != 'created':
|
||||
raise ValueError("Attribute 'party' may not be set in the state '%s'" %
|
||||
self.state)
|
||||
else:
|
||||
self.userName = party
|
||||
indexChanged = True
|
||||
self.checkOverwrite(kw)
|
||||
updatePlanData = False
|
||||
indexChanged = False
|
||||
data = self.data
|
||||
for k, v in kw.items():
|
||||
data[k] = v
|
||||
if k.startswith('plan'):
|
||||
updatePlanData = True
|
||||
if self.planStart is not None and self.planStart != self.timeStamp:
|
||||
self.timeStamp = self.planStart
|
||||
indexChanged = True
|
||||
if updatePlanData and self.planStart:
|
||||
data['planEnd'], data['planDuration'], data['planEffort'] = \
|
||||
recalcTimes(self.planStart, self.planEnd,
|
||||
self.planDuration, self.planEffort)
|
||||
if indexChanged:
|
||||
self.reindex()
|
||||
|
||||
def assign(self, party=None):
|
||||
self.doTransition('assign')
|
||||
self.data['assigned'] = getTimeStamp()
|
||||
if party is not None:
|
||||
self.userName = party
|
||||
self.reindex()
|
||||
|
||||
def startWork(self, **kw):
|
||||
self.checkOverwrite(kw)
|
||||
self.doTransition('start')
|
||||
start = self.data['start'] = kw.pop('start', None) or getTimeStamp()
|
||||
self.timeStamp = start
|
||||
self.reindex()
|
||||
|
||||
def stopWork(self, transition='finish', **kw):
|
||||
self.checkOverwrite(kw)
|
||||
self.doTransition(transition)
|
||||
self.reindex()
|
||||
|
||||
def reindex(self):
|
||||
getParent(self).updateTrack(self, {}) # force reindex
|
||||
|
||||
def checkOverwrite(self, kw):
|
||||
for k, v in kw.items():
|
||||
#old = data.get(k)
|
||||
old = getattr(self, k, None)
|
||||
if old is not None and old != v:
|
||||
raise ValueError("Attribute '%s' already set to '%s'." % (k, old))
|
||||
|
||||
|
||||
def recalcTimes(start, end, duration, effort):
|
||||
if duration is None and start and end:
|
||||
duration = end - start
|
||||
if end is None and start and duration:
|
||||
end = start + duration
|
||||
if effort is None and duration:
|
||||
effort = duration
|
||||
return end, duration, effort
|
||||
|
||||
|
||||
class WorkItems(object):
|
||||
|
|
|
@ -74,6 +74,8 @@ class Track(Persistent):
|
|||
self.data = data
|
||||
|
||||
def update(self, newData):
|
||||
if not newData:
|
||||
return
|
||||
self.timeStamp = getTimeStamp()
|
||||
data = self.data
|
||||
data.update(newData)
|
||||
|
@ -186,6 +188,8 @@ class TrackingStorage(BTreeContainer):
|
|||
return self.updateTrack(track, data)
|
||||
trackId, trackNum = self.generateTrackId()
|
||||
track = self.trackFactory(taskId, runId, userName, data)
|
||||
track.__parent__ = self
|
||||
track.__name__ = trackId
|
||||
if timeStamp:
|
||||
track.timeStamp = timeStamp
|
||||
self[trackId] = track
|
||||
|
|
Loading…
Add table
Reference in a new issue