diff --git a/entity.py b/entity.py deleted file mode 100644 index 1625b93..0000000 --- a/entity.py +++ /dev/null @@ -1,45 +0,0 @@ -# -# Copyright (c) 2004 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# - -""" -Definition of the Entity class. - -$Id: entity.py $ -""" - -from zope.interface import implements -from zope.app.container.ordered import OrderedContainer - -from interfaces import IEntity - - -class Entity(OrderedContainer): - """ A Task is a scheduled piece of work. - Resources may be allocated to a Task. - A Task may depend on subtasks. - """ - - implements(IEntity) - - def getRelations(self, relationships=None): - return [] - - def getReverseRelations(self, relationships=None): - return [] - - diff --git a/interfaces.py b/interfaces.py index f43b864..50525f0 100644 --- a/interfaces.py +++ b/interfaces.py @@ -27,47 +27,9 @@ from zope.app.container.interfaces import IOrderedContainer from zope.schema import TextLine -class IEntity(IOrderedContainer): - """ Common base class of the Task and Resource classes. - (Not sure if we really need it...) - """ - - def getRelations(relationships=None): - """ Return a list of target objects from relations assiociated - with this Entity, possibly restricted to the relationships given. - """ - - def getReverseRelations(relationships=None): - """ Return a list of source objects from relations directed at - this Entity as the target, possibly restricted to the relationships - given. - """ - -class DummyIEntity: - def getRelation(target, relationship=None): - """ Return the relation object specified by target and relationship. - """ - - def addRelation(target, relationship, **props): - """ Create a new relation object with relationship to target - and assign it to self. - If supported by relationship additional properties may be - given as keyword parameters. - Return relation object. - """ - - def removeRelation(target, relationship): - """ Remove the relation to target with relationship from self. - """ - - def getController(): - """ Return the LoopsController object of this Entity, typically - the parent LoopsManager object or the portal_loops Tool. - """ - - class ITask(IOrderedContainer): - """ A Task is a scheduled piece of work. + """ A Task is a piece of work. + Resources may be allocated to a Task. A Task may depend on subtasks. """ @@ -79,27 +41,23 @@ class ITask(IOrderedContainer): required=True) def getSubtasks(taskTypes=None): - """ Return a list of subtasks of self, + """ Return a tuple of subtasks of self, possibly restricted to the task types given. """ def assignSubtask(task): - """ Assign an existing task to self as a subtask. - Return the relation object that leads to the subtask (Really?). + """ Assign an existing task to self as a subtask.. """ def getParentTasks(): - """ Return a list of tasks to which self has a subtask relationship. + """ Return a tuple of tasks to which self has a subtask relationship. """ -class DummyITask: - - def createSubtask(taskType=None, container=None, id=None, **props): + def createSubtask(taskType=None, container=None, name=None): """ Create a new task with id in container and assign it to self as a subtask. container defaults to parent of self. - id will be generated if not given. - Return the relation object that leads to the subtask - (fetch the subtask via relation.getTarget()). + name will be generated if not given. + Return the new subtask. """ def deassignSubtask(task): @@ -107,15 +65,19 @@ class DummyITask: """ def getAllocatedResources(allocTypes=None, resTypes=None): - """ Return a list of resources allocated to self, + """ Return a tuple of resources allocated to self, possibly restricted to the allocation types and target resource types given. """ - def allocateResource(resource, allocType=None, **props): + def allocateResource(resource, allocType=None): + """ Allocate resource to self. A special allocation type may be given. + """ + + def createAndAllocateResource(resourceType='Resource', allocType='standard', + container=None, name=None): """ Allocate resource to self. A special allocation type may be given. Additional properties may be given as keyword parameters. - Return relation object that implements the allocation reference. """ def deallocateResource(resource): @@ -123,7 +85,7 @@ class DummyITask: """ def allocatedUserIds(): - """ Returns list of user IDs of allocated Person objects that are portal members. + """ Returns tuple of user IDs of allocated Person objects that are portal members. Used by catalog index 'allocUserIds'. """ @@ -134,5 +96,18 @@ class DummyITask: def getAllAllocTypes(): """ Return a tuple with all available allocation types defined - in the LoopsController object that is responsible for self. + in the controller object that is responsible for self. """ + + +class IResource(IOrderedContainer): + """ A Resource is an object - a thing or a person - that may be + allocated to one or more Task objects. + """ + + def getTasksAllocatedTo(allocTypes=None, taskTypes=None): + """ Return a list of task to which self is allocated to, + possibly restricted to the allocation types and + source task types given. + """ + diff --git a/resource.py b/resource.py new file mode 100644 index 0000000..a68387e --- /dev/null +++ b/resource.py @@ -0,0 +1,64 @@ +# +# Copyright (c) 2004 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 +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Definition of the Resource class. + +$Id$ +""" + +from zope.interface import implements +from zope.app.container.ordered import OrderedContainer + +from interfaces import IResource + + +class Resource(OrderedContainer): + + implements(IResource) + + title = u'' + + + def __init__(self): + OrderedContainer.__init__(self) + self._allocations = {} + + + def getTasksAllocatedTo(self, allocTypes=None, taskTypes=None): + from sets import Set + allocs = self._allocations + res = Set() + for at in allocs.keys(): + if allocTypes is None or at in allocTypes: + res.union_update(allocs[at]) + return tuple(res) + + + # Helper methods: + + def _updateAllocations(self, task, allocType): + #called by Task.allocateResource + tList = self._allocations.get(allocType, []) + tList.append(task) + self._allocations[allocType] = tList + + def _createResourceName(self): + prefix = 'rsc' + last = max([ int(n[len(prefix):]) for n in self.__parent__.keys() ] or [1]) + return prefix + str(last+1) diff --git a/task.py b/task.py index 3e40caa..edf4001 100644 --- a/task.py +++ b/task.py @@ -23,24 +23,25 @@ $Id$ """ from zope.interface import implements +from zope.app.container.ordered import OrderedContainer -from entity import Entity from interfaces import ITask -class Task(Entity): - """ A Task is a scheduled piece of work. - Resources may be allocated to a Task. - A Task may depend on subtasks. - """ +class Task(OrderedContainer): implements(ITask) title = u'' - #title = property(_getTitle, _setTitle) - _subtasks = [] - _parentTasks = [] + + def __init__(self): + OrderedContainer.__init__(self) + self._subtasks = [] + self._parentTasks = [] + self._resourceAllocs = {} + + # subtasks: def getSubtasks(self, taskTypes=None): return tuple(self._subtasks) @@ -51,4 +52,58 @@ class Task(Entity): def assignSubtask(self, task): self._subtasks.append(task) task._parentTasks.append(self) + + def createSubtask(self, taskType=None, container=None, name=None): + container = container or self.__parent__ + task = Task() + name = name or self._createTaskName() + container[name] = task + self.assignSubtask(task) return task + + def deassignSubtask(self, task): + self._subtasks.remove(task) + task._parentTasks.remove(self) + + # resources: + + def getAllocatedResources(self, allocTypes=None, resTypes=None): + from sets import Set + allocs = self._resourceAllocs + res = Set() + for at in allocs.keys(): + if allocTypes is None or at in allocTypes: + res.union_update(allocs[at]) + return tuple(res) + + def allocateResource(self, resource, allocType=None): + allocType = allocType or 'standard' + allocs = self._resourceAllocs + rList = allocs.get(allocType, []) + rList.append(resource) + allocs[allocType] = rList + resource._updateAllocations(self, allocType) + + def createAndAllocateResource(self, resourceType='Resource', allocType='standard', + container=None, id=None): + return None + + def deallocateResource(self, resource): + pass + + def allocatedUserIds(self): + return () + + def getAllocType(self, resource): + return 'standard' + + def getAllAllocTypes(self): + return ('standard',) + + + # Helper methods: + + def _createTaskName(self): + prefix = 'tsk' + last = max([ int(n[len(prefix):]) for n in self.__parent__.keys() ] or [1]) + return prefix + str(last+1) diff --git a/tests/test_resource.py b/tests/test_resource.py new file mode 100755 index 0000000..a9871be --- /dev/null +++ b/tests/test_resource.py @@ -0,0 +1,65 @@ +# $Id$ + +import unittest +#from zope.testing.doctestunit import DocTestSuite +from zope.interface.verify import verifyClass +#from zope.app.tests.setup import placelessSetUp +#from zope.app.container.tests.test_icontainer import TestSampleContainer +from zope.app.container.interfaces import IContained +from zope.app.folder import Folder + +from loops.resource import Resource +from loops.task import Task +from loops.interfaces import IResource + +#class Test(TestSampleContainer): +class Test(unittest.TestCase): + "Test methods of the Resource class." + + def setUp(self): + #placelessSetUp() + self.f1 = Folder() + self.f1.__name__ = u'f1' + self.r1 = Resource() + self.f1['rsc1'] = self.r1 + self.t1 = Task() + self.f1['tsk1'] = self.t1 + + def tearDown(self): + pass + + # the tests... + + def testInterface(self): + self.assert_(IResource.providedBy(Resource()), + 'Interface IResource is not implemented by class Resource.') + self.assert_(IContained.providedBy(Resource()), + 'Interface IContained is not implemented by class Resource.') + verifyClass(IResource, Resource) + + def testContained(self): + self.assertEqual(u'rsc1', self.r1.__name__) + self.assertEqual(u'f1', self.r1.__parent__.__name__) + + def testTitle(self): + r = Resource() + self.assertEqual(u'', r.title) + r.title = u'First Resource' + self.assertEqual(u'First Resource', r.title) + + def testAllocateToTask(self): + t1 = self.t1 + r1 = self.r1 + self.assertEqual((), r1.getTasksAllocatedTo()) + t1.allocateResource(r1) + self.assertEqual((t1,), r1.getTasksAllocatedTo()) + + +def test_suite(): + return unittest.TestSuite(( +# DocTestSuite('loops.tests.doctests'), + unittest.makeSuite(Test), + )) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/tests/test_task.py b/tests/test_task.py index 1a9c4a9..88b379a 100755 --- a/tests/test_task.py +++ b/tests/test_task.py @@ -1,21 +1,43 @@ # $Id$ import unittest -from zope.testing.doctestunit import DocTestSuite -from zope.app.container.tests.test_icontainer import TestSampleContainer +#from zope.testing.doctestunit import DocTestSuite from zope.interface.verify import verifyClass +#from zope.app.tests.setup import placelessSetUp +#from zope.app.container.tests.test_icontainer import TestSampleContainer +from zope.app.container.interfaces import IContained +from zope.app.folder import Folder from loops.task import Task from loops.interfaces import ITask #class Test(TestSampleContainer): -class Test(unittest.TestCase): +class TestTask(unittest.TestCase): "Test methods of the Task class." + def setUp(self): + #placelessSetUp() + self.f1 = Folder() + self.f1.__name__ = u'f1' + self.t1 = Task() + self.f1['tsk1'] = self.t1 + + def tearDown(self): + pass + + # the tests... + def testInterface(self): - self.assert_(ITask.providedBy(Task()), 'Interface ITask is not implemented by class Task.') + self.assert_(ITask.providedBy(Task()), + 'Interface ITask is not implemented by class Task.') + self.assert_(IContained.providedBy(Task()), + 'Interface IContained is not implemented by class Task.') verifyClass(ITask, Task) + def testContained(self): + self.assertEqual(u'tsk1', self.t1.__name__) + self.assertEqual(u'f1', self.t1.__parent__.__name__) + def testTitle(self): t = Task() self.assertEqual(u'', t.title) @@ -24,18 +46,63 @@ class Test(unittest.TestCase): def testSubtasks(self): t1 = Task() - self.assertEquals((), t1.getSubtasks()) + self.assertEqual((), t1.getSubtasks()) t2 = Task() - self.assertEquals((), t2.getSubtasks()) - self.assertEquals((), t2.getParentTasks()) + self.assertEqual((), t2.getSubtasks()) + self.assertEqual((), t2.getParentTasks()) t1.assignSubtask(t2) - self.assertEquals((t2,), t1.getSubtasks()) - self.assertEquals((t1,), t2.getParentTasks()) + self.assertEqual((t2,), t1.getSubtasks()) + self.assertEqual((t1,), t2.getParentTasks()) + + def testCreateSubtask(self): + t1 = self.t1 + self.assertEqual((), t1.getSubtasks()) + t2 = t1.createSubtask() + self.assertEqual((t1,), t2.getParentTasks()) + self.assertEqual((t2,), t1.getSubtasks()) + + def testDeassignSubtask(self): + t1 = self.t1 + t2 = t1.createSubtask() + self.assertEqual((t1,), t2.getParentTasks()) + t1.deassignSubtask(t2) + self.assertEqual((), t2.getParentTasks()) + self.assertEqual((), t1.getSubtasks()) + + +from loops.resource import Resource + +class TestTaskResource(unittest.TestCase): + "Test methods of the Task class related to Resource allocations." + + + def setUp(self): + #placelessSetUp() + self.f1 = Folder() + self.f1.__name__ = u'f1' + self.t1 = Task() + self.r1 = Resource() + self.f1['tsk1'] = self.t1 + self.r1['rsc1'] = self.r1 + + def tearDown(self): + pass + + # the tests... + + def testAllocateResource(self): + t1 = self.t1 + r1 = self.r1 + self.assertEqual((), t1.getAllocatedResources()) + t1.allocateResource(r1) + self.assertEqual((r1,), t1.getAllocatedResources()) + def test_suite(): return unittest.TestSuite(( - DocTestSuite('loops.tests.doctests'), - unittest.makeSuite(Test), +# DocTestSuite('loops.tests.doctests'), + unittest.makeSuite(TestTask), + unittest.makeSuite(TestTaskResource), )) if __name__ == '__main__':