diff --git a/interfaces.py b/interfaces.py index a893715..0bd5a67 100644 --- a/interfaces.py +++ b/interfaces.py @@ -25,7 +25,7 @@ $Id$ from zope.interface import Interface from zope.app.container.interfaces import IOrderedContainer -from zope.schema import Text, TextLine, List, Object +from zope.schema import Text, TextLine, List, Object, Int class IResourceConstraint(Interface): @@ -93,6 +93,20 @@ class ITask(IOrderedContainer): default=u'', required=True) + qualifier = TextLine( + title=u'Qualifier', + description=u'Some string telling more specifically what this task is about', + default=u'', + required=False) + # to do: associate with a dynamically provided vocabulary + + priority = Int( + title=u'Priority', + description=u'The priority is usually used for ordering the listing ' + 'of tasks or subtasks; 0 means no priority, lower number = higher priority', + default=0, + required=False) + resourceConstraints = List( title=u'Resource Constraints', description=u'Collection of Constraints controlling the resources ' @@ -196,6 +210,12 @@ class ITask(IOrderedContainer): and usable resource constraints are present. """ + def isValid(checkSubtasks=True): + """ Check if currently assigned resources fulfill the resource constraints. + + Default: Also check subtasks. + """ + # Task object as prototype: def copyTask(targetContainer=None): diff --git a/task.py b/task.py index cf71ca4..bc8a0eb 100644 --- a/task.py +++ b/task.py @@ -37,6 +37,8 @@ class Task(OrderedContainer): implements(ITask) title = u'' + qualifier = u'' + priority = 0 def __init__(self): @@ -49,7 +51,9 @@ class Task(OrderedContainer): # subtasks: def getSubtasks(self, taskTypes=None): - return tuple(self._subtasks) + st = self._subtasks + st.sort(lambda x,y: x.priority < y.priority and -1 or 1) + return tuple(st) def getParentTasks(self, taskTypes=None): return tuple(self._parentTasks) @@ -147,6 +151,17 @@ class Task(OrderedContainer): return result and result or None return tuple([ c for c in candidates if self.isResourceAllowed(c) ]) + def isValid(self, checkSubtasks=True): + if self.resourceConstraints is not None: + for r in self.getAllocatedResources(): + if not self.isResourceAllowed(r): + return False + if checkSubtasks: + for t in self.getSubtasks(): + if not t.isValid(): + return False + return True + # Task object as prototype: def copyTask(self, targetContainer=None): diff --git a/tests/test_task.py b/tests/test_task.py index d9702a2..381b8d6 100755 --- a/tests/test_task.py +++ b/tests/test_task.py @@ -47,6 +47,12 @@ class TestTask(unittest.TestCase): t.title = u'First Task' self.assertEqual(u'First Task', t.title) + def testQualifier(self): + t = Task() + self.assertEqual(u'', t.qualifier) + t.qualifier = u'troubleticket' + self.assertEqual(u'troubleticket', t.qualifier) + def testSubtasks(self): t1 = Task() self.assertEqual((), t1.getSubtasks()) @@ -72,6 +78,15 @@ class TestTask(unittest.TestCase): self.assertEqual((), t2.getParentTasks()) self.assertEqual((), t1.getSubtasks()) + def testSubtasksOrdering(self): + t1 = self.t1 + st1 = t1.createSubtask() + st2 = t1.createSubtask() + st2.priority = 2 + self.assertEqual((st1, st2), t1.getSubtasks()) + st1.priority = 3 + self.assertEqual((st2, st1), t1.getSubtasks()) + from loops.resource import Resource @@ -154,6 +169,29 @@ class TestTaskResourceConstraints(unittest.TestCase): self.assertEqual((r1,), t1.getCandidateResources()) self.assertEqual((r1,), rc1.getAllowedResources([r1])) + def testIsValid(self): + t1 = self.t1 + r1 = self.r1 + rc1 = self.rc1 + t1.allocateResource(r1) + self.failUnless(t1.isValid()) # no constraint - everything valid + self.t1.resourceConstraints.append(self.rc1) # empty constraint + self.failIf(t1.isValid()) # does not allow anything + rc1.referenceValues = ([r1]) # explicit allow + self.failUnless(t1.isValid()) # + + def testIsValidParentTask(self): + t1 = self.t1 + r1 = self.r1 + rc1 = self.rc1 + t1.allocateResource(r1) + pt = Task() + pt.assignSubtask(t1) + self.failUnless(pt.isValid()) # no constraint - everything valid + self.t1.resourceConstraints.append(self.rc1) # empty constraint + self.failIf(pt.isValid()) # does not allow anything + self.failUnless(pt.isValid(False)) # don't check subtasks + class TestTaskCopy(unittest.TestCase): "Tests related to copying tasks e.g. when using task prototypes."