Resource constraints added to Task
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@112 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
3dfee96db3
commit
f4cc0c132c
6 changed files with 230 additions and 54 deletions
|
@ -37,12 +37,11 @@ class ResourceConstraint(object):
|
||||||
referenceKey = None
|
referenceKey = None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, task):
|
def __init__(self):
|
||||||
self.referenceValues = []
|
self.referenceValues = []
|
||||||
self._task = task
|
|
||||||
|
|
||||||
|
|
||||||
def isResourceAllowed(self, resource):
|
def isResourceAllowed(self, resource, task=None):
|
||||||
if self.referenceType == 'parent':
|
if self.referenceType == 'parent':
|
||||||
for r in self.referenceValues:
|
for r in self.referenceValues:
|
||||||
m = getattr(r, self.referenceKey)
|
m = getattr(r, self.referenceKey)
|
||||||
|
@ -55,7 +54,7 @@ class ResourceConstraint(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def getAllowedResources(self, candidates=None):
|
def getAllowedResources(self, candidates=None, task=None):
|
||||||
if self.referenceType == 'parent':
|
if self.referenceType == 'parent':
|
||||||
result = []
|
result = []
|
||||||
for r in self.referenceValues:
|
for r in self.referenceValues:
|
||||||
|
|
142
interfaces.py
142
interfaces.py
|
@ -28,6 +28,58 @@ from zope.app.container.interfaces import IOrderedContainer
|
||||||
from zope.schema import Text, TextLine, List, Object
|
from zope.schema import Text, TextLine, List, Object
|
||||||
|
|
||||||
|
|
||||||
|
class IResourceConstraint(Interface):
|
||||||
|
""" A ResourceConstraint governs which Resource objects may be
|
||||||
|
allocated to a Task object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
explanation = Text(
|
||||||
|
title=u'Explanation',
|
||||||
|
description=u'Explanation of this constraint - '
|
||||||
|
'why or why not certain resources may be allowed',
|
||||||
|
required=True)
|
||||||
|
|
||||||
|
constraintType = TextLine(
|
||||||
|
title=u'Constraint Type',
|
||||||
|
description=u'Type of the constraint: select, require, disallow',
|
||||||
|
default=u'select',
|
||||||
|
required=True)
|
||||||
|
|
||||||
|
referenceType = TextLine(
|
||||||
|
title=u'Reference Type',
|
||||||
|
description=u'Type of reference to the resource attribute to check: '
|
||||||
|
'explicit, parent, type, attribute, property, method',
|
||||||
|
default=u'explicit',
|
||||||
|
required=True)
|
||||||
|
|
||||||
|
referenceKey = TextLine(
|
||||||
|
title=u'Reference Key',
|
||||||
|
description=u'Key for referencing the resource attribute')
|
||||||
|
|
||||||
|
referenceValues = List(
|
||||||
|
title=u'Reference Values',
|
||||||
|
description=u'Attribute values to check for; may be any kind of object',
|
||||||
|
value_type=Object(Interface, title=u'Value'),
|
||||||
|
unique=True)
|
||||||
|
|
||||||
|
def isResourceAllowed(resource, task=None):
|
||||||
|
""" Return True if this ResourceConstraint allows the resource given.
|
||||||
|
|
||||||
|
If a task parameter is given there may be special checks on it, e.g.
|
||||||
|
on concerning subtasks of the task's parent task (sibling constraints).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def getAllowedResources(candidates=None, task=None):
|
||||||
|
""" Return a list of resource objects that are allowed by this
|
||||||
|
ResourceConstraint.
|
||||||
|
|
||||||
|
If given, use candidates as a list of possible resources
|
||||||
|
(candidates must implement the IResource interface).
|
||||||
|
If a task parameter is given there may be special checks on it, e.g.
|
||||||
|
on concerning subtasks of the task's parent task (sibling constraints).
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ITask(IOrderedContainer):
|
class ITask(IOrderedContainer):
|
||||||
""" A Task is a piece of work.
|
""" A Task is a piece of work.
|
||||||
|
|
||||||
|
@ -41,6 +93,17 @@ class ITask(IOrderedContainer):
|
||||||
default=u'',
|
default=u'',
|
||||||
required=True)
|
required=True)
|
||||||
|
|
||||||
|
resourceConstraints = List(
|
||||||
|
title=u'Resource Constraints',
|
||||||
|
description=u'Collection of Constraints controlling the resources '
|
||||||
|
'that may be allocated to a task',
|
||||||
|
default=[],
|
||||||
|
required=False,
|
||||||
|
value_type=Object(schema=IResourceConstraint, title=u'Resource Constraint'),
|
||||||
|
unique=True)
|
||||||
|
|
||||||
|
# subtask stuff:
|
||||||
|
|
||||||
def getSubtasks(taskTypes=None):
|
def getSubtasks(taskTypes=None):
|
||||||
""" Return a tuple of subtasks of self,
|
""" Return a tuple of subtasks of self,
|
||||||
possibly restricted to the task types given.
|
possibly restricted to the task types given.
|
||||||
|
@ -67,6 +130,8 @@ class ITask(IOrderedContainer):
|
||||||
""" Remove the subtask relation to task from self.
|
""" Remove the subtask relation to task from self.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# resource allocations:
|
||||||
|
|
||||||
def getAllocatedResources(allocTypes=None, resTypes=None):
|
def getAllocatedResources(allocTypes=None, resTypes=None):
|
||||||
""" Return a tuple of resources allocated to self,
|
""" Return a tuple of resources allocated to self,
|
||||||
possibly restricted to the allocation types and
|
possibly restricted to the allocation types and
|
||||||
|
@ -109,6 +174,38 @@ class ITask(IOrderedContainer):
|
||||||
in the controller object that is responsible for self.
|
in the controller object that is responsible for self.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# resource constraint stuff:
|
||||||
|
|
||||||
|
def isResourceAllowed(resource):
|
||||||
|
""" Return True if the resource given is allowed for this task.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def getCandidateResources():
|
||||||
|
""" Return a tuple of resource objects that are allowed for this task.
|
||||||
|
|
||||||
|
Returns empty tuple if no usable resource constraints present.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def getAllowedResources(candidates=None):
|
||||||
|
""" Return a list of resource objects that are allowed for this task.
|
||||||
|
|
||||||
|
If given, use candidates as a list of possible resources
|
||||||
|
(candidates must implement the IResource interface).
|
||||||
|
Returns None if no usable resource constraints are present.
|
||||||
|
Falls back to getCandidateResources if candidates is None
|
||||||
|
and usable resource constraints are present.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Task object as prototype:
|
||||||
|
|
||||||
|
def copyTask(targetContainer=None):
|
||||||
|
""" Copy self to the target container given and return the new task.
|
||||||
|
|
||||||
|
Also copy all subtasks. Keep the references to resources and
|
||||||
|
resource constraints without copying them.
|
||||||
|
targetContainer defaults to self.getParent().
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class IResource(IOrderedContainer):
|
class IResource(IOrderedContainer):
|
||||||
""" A Resource is an object - a thing or a person - that may be
|
""" A Resource is an object - a thing or a person - that may be
|
||||||
|
@ -122,48 +219,3 @@ class IResource(IOrderedContainer):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class IResourceConstraint(Interface):
|
|
||||||
""" A ResourceConstraint governs which Resource objects may be
|
|
||||||
allocated to a Task object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
explanation = Text(
|
|
||||||
title=u'Explanation',
|
|
||||||
description=u'Explanation of this constraint - '
|
|
||||||
'why or why not certain resources may be allowed',
|
|
||||||
required=True)
|
|
||||||
|
|
||||||
constraintType = TextLine(
|
|
||||||
title=u'Constraint Type',
|
|
||||||
description=u'Type of the constraint: select, require, disallow',
|
|
||||||
default=u'select',
|
|
||||||
required=True)
|
|
||||||
|
|
||||||
referenceType = TextLine(
|
|
||||||
title=u'Reference Type',
|
|
||||||
description=u'Type of reference to the resource attribute to check: '
|
|
||||||
'explicit, parent, type, attribute, property, method'
|
|
||||||
default=u'explicit',
|
|
||||||
required=True)
|
|
||||||
|
|
||||||
referenceKey = TextLine(
|
|
||||||
title=u'Reference Key',
|
|
||||||
description=u'Key for referencing the resource attribute')
|
|
||||||
|
|
||||||
referenceValues = List(
|
|
||||||
title=u'Reference Values',
|
|
||||||
description=u'Attribute values to check for; may be any kind of object',
|
|
||||||
value_type=Object(Interface, title=u'Value'))
|
|
||||||
|
|
||||||
def isResourceAllowed(resource):
|
|
||||||
""" Return True if this ResourceConstraint allows the resource given.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def getAllowedResources(candidates=None):
|
|
||||||
""" Return a list of resource objects that are allowed by this
|
|
||||||
ResourceConstraint.
|
|
||||||
|
|
||||||
If given, use candidates as a list of possible resources
|
|
||||||
(candidates must implement the IResource interface).
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ class Resource(OrderedContainer):
|
||||||
def _createResourceName(self, container=None):
|
def _createResourceName(self, container=None):
|
||||||
prefix = 'rsc'
|
prefix = 'rsc'
|
||||||
container = container or zapi.getParent(self)
|
container = container or zapi.getParent(self)
|
||||||
last = max([ int(n[len(prefix):]) for n in container.keys() ] or [1])
|
last = max([ int(n[len(prefix):]) for n in container.keys() ] or [0])
|
||||||
return prefix + str(last+1)
|
return prefix + str(last+1)
|
||||||
|
|
||||||
|
|
50
task.py
50
task.py
|
@ -24,11 +24,14 @@ $Id$
|
||||||
|
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
from zope.app.container.ordered import OrderedContainer
|
from zope.app.container.ordered import OrderedContainer
|
||||||
|
from zope.app.copypastemove import ObjectCopier
|
||||||
from zope.app import zapi
|
from zope.app import zapi
|
||||||
|
|
||||||
from resource import Resource
|
from resource import Resource
|
||||||
from interfaces import ITask
|
from interfaces import ITask
|
||||||
|
|
||||||
|
from copy import copy
|
||||||
|
|
||||||
class Task(OrderedContainer):
|
class Task(OrderedContainer):
|
||||||
|
|
||||||
implements(ITask)
|
implements(ITask)
|
||||||
|
@ -41,6 +44,7 @@ class Task(OrderedContainer):
|
||||||
self._subtasks = []
|
self._subtasks = []
|
||||||
self._parentTasks = []
|
self._parentTasks = []
|
||||||
self._resourceAllocs = {}
|
self._resourceAllocs = {}
|
||||||
|
self.resourceConstraints = []
|
||||||
|
|
||||||
# subtasks:
|
# subtasks:
|
||||||
|
|
||||||
|
@ -112,11 +116,55 @@ class Task(OrderedContainer):
|
||||||
def getAllAllocTypes(self):
|
def getAllAllocTypes(self):
|
||||||
return ('standard',)
|
return ('standard',)
|
||||||
|
|
||||||
|
# resource constraint stuff:
|
||||||
|
|
||||||
|
def isResourceAllowed(self, resource):
|
||||||
|
rc = self.resourceConstraints
|
||||||
|
if not rc:
|
||||||
|
return True
|
||||||
|
for c in rc:
|
||||||
|
# that's too simple, we must check all constraints for constraintType:
|
||||||
|
if c.isResourceAllowed(resource):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getCandidateResources(self):
|
||||||
|
rc = self.resourceConstraints
|
||||||
|
if not rc:
|
||||||
|
return ()
|
||||||
|
result = []
|
||||||
|
for c in rc:
|
||||||
|
result.extend(c.getAllowedResources())
|
||||||
|
return tuple(result)
|
||||||
|
|
||||||
|
def getAllowedResources(self, candidates=None):
|
||||||
|
rc = self.resourceConstraints
|
||||||
|
if not rc:
|
||||||
|
return None
|
||||||
|
if candidates is None:
|
||||||
|
result = self.getCandidateResources()
|
||||||
|
# Empty result means: can't tell
|
||||||
|
return result and result or None
|
||||||
|
return tuple([ c for c in candidates if self.isResourceAllowed(c) ])
|
||||||
|
|
||||||
|
# Task object as prototype:
|
||||||
|
|
||||||
|
def copyTask(self, targetContainer=None):
|
||||||
|
targetContainer = targetContainer or zapi.getParent(self)
|
||||||
|
newName = self._createTaskName(targetContainer)
|
||||||
|
newTask = copy(self)
|
||||||
|
targetContainer[newName] = newTask
|
||||||
|
newTask._subtasks = []
|
||||||
|
for st in self.getSubtasks():
|
||||||
|
newSt = st.copyTask(targetContainer)
|
||||||
|
newSt._parentTasks.remove(self)
|
||||||
|
newTask.assignSubtask(newSt)
|
||||||
|
return newTask
|
||||||
|
|
||||||
# Helper methods:
|
# Helper methods:
|
||||||
|
|
||||||
def _createTaskName(self, container=None):
|
def _createTaskName(self, container=None):
|
||||||
prefix = 'tsk'
|
prefix = 'tsk'
|
||||||
container = container or zapi.getParent(self)
|
container = container or zapi.getParent(self)
|
||||||
last = max([ int(n[len(prefix):]) for n in container.keys() ] or [1])
|
last = max([ int(n[len(prefix):]) for n in container.keys() ] or [0])
|
||||||
return prefix + str(last+1)
|
return prefix + str(last+1)
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Test(unittest.TestCase):
|
||||||
self.f1['rsc1'] = self.r1
|
self.f1['rsc1'] = self.r1
|
||||||
self.t1 = Task()
|
self.t1 = Task()
|
||||||
self.f1['tsk1'] = self.t1
|
self.f1['tsk1'] = self.t1
|
||||||
self.rc1 = ResourceConstraint(self.t1)
|
self.rc1 = ResourceConstraint()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -86,7 +86,7 @@ class TestTaskResource(unittest.TestCase):
|
||||||
self.t1 = Task()
|
self.t1 = Task()
|
||||||
self.r1 = Resource()
|
self.r1 = Resource()
|
||||||
self.f1['tsk1'] = self.t1
|
self.f1['tsk1'] = self.t1
|
||||||
self.r1['rsc1'] = self.r1
|
self.f1['rsc1'] = self.r1
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
@ -115,11 +115,88 @@ class TestTaskResource(unittest.TestCase):
|
||||||
self.assertEqual((r1,), t1.getAllocatedResources())
|
self.assertEqual((r1,), t1.getAllocatedResources())
|
||||||
|
|
||||||
|
|
||||||
|
from loops.constraint import ResourceConstraint
|
||||||
|
|
||||||
|
class TestTaskResourceConstraints(unittest.TestCase):
|
||||||
|
"Test methods of the Task class related to checking for allowed resources."
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.f1 = Folder()
|
||||||
|
self.f1.__name__ = u'f1'
|
||||||
|
self.t1 = Task()
|
||||||
|
self.r1 = Resource()
|
||||||
|
self.f1['tsk1'] = self.t1
|
||||||
|
self.r1['rsc1'] = self.r1
|
||||||
|
self.rc1 = ResourceConstraint()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# the tests...
|
||||||
|
|
||||||
|
def testSelectExplicit(self):
|
||||||
|
t1 = self.t1
|
||||||
|
r1 = self.r1
|
||||||
|
rc1 = self.rc1
|
||||||
|
|
||||||
|
self.assertEqual(True, t1.isResourceAllowed(r1)) # no check
|
||||||
|
self.assertEqual((), t1.getCandidateResources()) # no candidates
|
||||||
|
self.assertEqual(None, t1.getAllowedResources([r1]))# can't say without constraint
|
||||||
|
|
||||||
|
self.t1.resourceConstraints.append(self.rc1) # empty constraint
|
||||||
|
self.assertEqual(False, t1.isResourceAllowed(r1)) # does not allow
|
||||||
|
self.assertEqual((), t1.getCandidateResources()) # anything
|
||||||
|
self.assertEqual((), t1.getAllowedResources([r1]))
|
||||||
|
|
||||||
|
rc1.referenceValues = ([r1])
|
||||||
|
self.assertEqual(True, rc1.isResourceAllowed(r1))
|
||||||
|
self.assertEqual((r1,), t1.getCandidateResources())
|
||||||
|
self.assertEqual((r1,), rc1.getAllowedResources([r1]))
|
||||||
|
|
||||||
|
|
||||||
|
class TestTaskCopy(unittest.TestCase):
|
||||||
|
"Tests related to copying tasks e.g. when using task prototypes."
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.f1 = Folder()
|
||||||
|
self.f1.__name__ = u'f1'
|
||||||
|
self.t1 = Task()
|
||||||
|
self.r1 = Resource()
|
||||||
|
self.f1['tsk1'] = self.t1
|
||||||
|
self.f1['rsc1'] = self.r1
|
||||||
|
self.rc1 = ResourceConstraint()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# the tests...
|
||||||
|
|
||||||
|
def testCopyTask(self):
|
||||||
|
t1 = self.t1
|
||||||
|
r1 = self.r1
|
||||||
|
rc1 = self.rc1
|
||||||
|
ts1 = t1.createSubtask()
|
||||||
|
ts1.resourceConstraints.append(rc1)
|
||||||
|
ts1.allocateResource(r1)
|
||||||
|
t2 = t1.copyTask()
|
||||||
|
self.failIf(t1 is t2, 't1 and t2 are still the same')
|
||||||
|
st2 = t2.getSubtasks()
|
||||||
|
self.assertEquals(1, len(st2))
|
||||||
|
ts2 = st2[0]
|
||||||
|
self.failIf(ts1 is ts2, 'ts1 and ts2 are still the same')
|
||||||
|
self.assertEquals((r1,), ts2.getAllocatedResources())
|
||||||
|
self.assertEquals([rc1,], ts2.resourceConstraints)
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
return unittest.TestSuite((
|
return unittest.TestSuite((
|
||||||
# DocTestSuite('loops.tests.doctests'),
|
# DocTestSuite('loops.tests.doctests'),
|
||||||
unittest.makeSuite(TestTask),
|
unittest.makeSuite(TestTask),
|
||||||
unittest.makeSuite(TestTaskResource),
|
unittest.makeSuite(TestTaskResource),
|
||||||
|
unittest.makeSuite(TestTaskResourceConstraints),
|
||||||
|
unittest.makeSuite(TestTaskCopy),
|
||||||
))
|
))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Add table
Reference in a new issue