Work in progress: re-building loops package as a 'concept management framework'
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@809 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
2036521b66
commit
7a0a2e2790
9 changed files with 157 additions and 499 deletions
56
README.txt
56
README.txt
|
@ -3,21 +3,21 @@ loops - Linked Objects for Organizational Process Services
|
|||
|
||||
($Id$)
|
||||
|
||||
Tasks and Subtasks
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
Concepts and Relations
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Let's start with creating a few example tasks:
|
||||
Let's start with creating a few example concepts:
|
||||
|
||||
>>> from loops.task import Task
|
||||
>>> t1 = Task()
|
||||
>>> t1.title
|
||||
>>> from loops.concept import Concept
|
||||
>>> c1 = Concept()
|
||||
>>> c1.title
|
||||
u''
|
||||
|
||||
>>> t2 = Task(u't2', u'Second Task')
|
||||
>>> t2.title
|
||||
u'Second Task'
|
||||
>>> c2 = Concept(u'c2', u'Second Concept')
|
||||
>>> c2.title
|
||||
u'Second Concept'
|
||||
|
||||
Now we want to make the second task a subtask of the first one.
|
||||
Now we want to relate the second concept to the first one.
|
||||
|
||||
In order to do this we first have to provide a relations registry. For
|
||||
testing we use a simple dummy implementation.
|
||||
|
@ -25,28 +25,32 @@ testing we use a simple dummy implementation.
|
|||
>>> from cybertools.relation.interfaces import IRelationsRegistry
|
||||
>>> from cybertools.relation.registry import DummyRelationsRegistry
|
||||
>>> from zope.app.testing import ztapi
|
||||
|
||||
>>> ztapi.provideUtility(IRelationsRegistry, DummyRelationsRegistry())
|
||||
|
||||
Now we can assign the task t2 as a subtask to t1:
|
||||
We also need a Relation class to be used for connecting concepts:
|
||||
|
||||
>>> from cybertools.relation import DyadicRelation
|
||||
|
||||
Now we can assign the concept c2 to c1:
|
||||
|
||||
>>> t1.assignSubtask(t2)
|
||||
>>> c1.assignConcept(c2, DyadicRelation)
|
||||
|
||||
We can now ask our tasks for their subtasks and parent tasks:
|
||||
We can now ask our concepts for their related concepts:
|
||||
|
||||
>>> st1 = t1.getSubtasks()
|
||||
>>> len(st1)
|
||||
>>> sc1 = c1.getSubConcepts()
|
||||
>>> len(sc1)
|
||||
1
|
||||
>>> t2 in st1
|
||||
>>> c2 in sc1
|
||||
True
|
||||
>>> len(t1.getParentTasks())
|
||||
>>> len(c1.getParentConcepts())
|
||||
0
|
||||
|
||||
>>> pc2 = c2.getParentConcepts()
|
||||
>>> len(pc2)
|
||||
1
|
||||
|
||||
>>> c1 in pc2
|
||||
True
|
||||
>>> len(c2.getSubConcepts())
|
||||
0
|
||||
|
||||
>>> pt2 = t2.getParentTasks()
|
||||
>>> len(pt2)
|
||||
1
|
||||
>>> t1 in pt2
|
||||
True
|
||||
>>> len(t2.getSubtasks())
|
||||
0
|
||||
|
|
@ -26,7 +26,7 @@ from zope.app import zapi
|
|||
from zope.app.dublincore.interfaces import ICMFDublinCore
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
|
||||
from loops.interfaces import ITask
|
||||
from loops.interfaces import IConcept
|
||||
|
||||
class Details(object):
|
||||
|
||||
|
@ -38,15 +38,15 @@ class Details(object):
|
|||
return d and d.strftime('%Y-%m-%d %H:%M') or ''
|
||||
|
||||
|
||||
class SubtaskAssignments(Details):
|
||||
class ConceptRelations(Details):
|
||||
|
||||
def assignSubtask(self):
|
||||
""" Add a subtask denoted by the path given in the
|
||||
request variable subtaskPath.
|
||||
"""
|
||||
subtaskPath = self.request.get('subtaskPath')
|
||||
#if subtaskPath:
|
||||
subtask = zapi.traverse(zapi.getRoot(self.context), subtaskPath, None, self.request)
|
||||
#if subtask:
|
||||
self.context.assignSubtask(removeSecurityProxy(subtask))
|
||||
conceptName = self.request.get('concept_name')
|
||||
#if conceptName:
|
||||
concept = zapi.getParent(self.context)[conceptName]
|
||||
#if concept:
|
||||
self.context.assignConcept(removeSecurityProxy(concept))
|
||||
self.request.response.redirect('.')
|
||||
|
|
|
@ -6,24 +6,24 @@
|
|||
<body>
|
||||
<div metal:fill-slot="body">
|
||||
|
||||
<h1 tal:content="context/title">Task Title</h1>
|
||||
<h1 tal:content="context/title">Concept Title</h1>
|
||||
|
||||
<div class="row">
|
||||
<span class="label">Subtasks</span>:
|
||||
<span class="label">Sub-Concepts</span>:
|
||||
<span class="field"
|
||||
tal:repeat="task context/getSubtasks">
|
||||
<span tal:condition="python: task is None">**deleted**</span>
|
||||
<span tal:condition="python: task is not None"
|
||||
tal:content="task/title">subtask</span>
|
||||
<span class="field" tal:condition="not:repeat/task/end"> - </span>
|
||||
tal:repeat="concept context/getSubConcepts">
|
||||
<span tal:condition="python: concept is None">**deleted**</span>
|
||||
<span tal:condition="python: concept is not None"
|
||||
tal:content="concept/title">subconcept</span>
|
||||
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">Parent Tasks</span>:
|
||||
<span class="label">Parent Concepts</span>:
|
||||
<span class="field"
|
||||
tal:repeat="task context/getParentTasks">
|
||||
<span tal:content="task/title">parent task</span>
|
||||
<span class="field" tal:condition="not:repeat/task/end"> - </span>
|
||||
tal:repeat="concept context/getParentConcepts">
|
||||
<span tal:content="concept/title">parent concept</span>
|
||||
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
@ -34,13 +34,13 @@
|
|||
<form action="." method="post"
|
||||
tal:attributes="action context/@@absolute_url">
|
||||
<div class="row">
|
||||
<span class="label">Subtasks</span>:
|
||||
<span class="label">Concept Name</span>:
|
||||
<span class="field">
|
||||
<input type="test" name="subtaskPath" />
|
||||
<input type="test" name="concept_name" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="submit" name="subtask_assign:method" value="Assign Subtask" />
|
||||
<input type="submit" name="concept_assign:method" value="Assign Concept" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -6,24 +6,24 @@
|
|||
<body>
|
||||
<div metal:fill-slot="body">
|
||||
|
||||
<h1 tal:content="context/title">Task Title</h1>
|
||||
<h1 tal:content="context/title">Concept Title</h1>
|
||||
|
||||
<div class="row">
|
||||
<span class="label">Subtasks</span>:
|
||||
<span class="label">Sub-Concepts</span>:
|
||||
<span class="field"
|
||||
tal:repeat="task context/getSubtasks">
|
||||
<span tal:condition="python: task is None">**deleted**</span>
|
||||
<span tal:condition="python: task is not None"
|
||||
tal:content="task/title">subtask</span>
|
||||
<span class="field" tal:condition="not:repeat/task/end"> - </span>
|
||||
tal:repeat="concept context/getSubConcepts">
|
||||
<span tal:condition="python: concept is None">**deleted**</span>
|
||||
<span tal:condition="python: concept is not None"
|
||||
tal:content="concept/title">subtask</span>
|
||||
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">Parent Tasks</span>:
|
||||
<span class="label">Parent Concepts</span>:
|
||||
<span class="field"
|
||||
tal:repeat="task context/getParentTasks">
|
||||
<span tal:content="task/title">parent task</span>
|
||||
<span class="field" tal:condition="not:repeat/task/end"> - </span>
|
||||
tal:repeat="concept context/getParentConcepts">
|
||||
<span tal:content="concept/title">parent concept</span>
|
||||
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
|
|
@ -7,67 +7,60 @@
|
|||
>
|
||||
|
||||
<addform
|
||||
label="Add Task"
|
||||
name="AddTask.html"
|
||||
schema="loops.interfaces.ITask"
|
||||
content_factory="loops.task.Task"
|
||||
label="Add Concept"
|
||||
name="AddLoopsConcept.html"
|
||||
schema="loops.interfaces.IConcept"
|
||||
content_factory="loops.concept.Concept"
|
||||
fields="title"
|
||||
permission="zope.ManageContent"
|
||||
/>
|
||||
|
||||
<addMenuItem
|
||||
class="loops.task.Task"
|
||||
title="Task"
|
||||
description="A Task is a piece of work or something else a resource may be allocated to"
|
||||
class="loops.concept.Concept"
|
||||
title="Concept"
|
||||
description="A Concept is a Concept is a Concept..."
|
||||
permission="zope.ManageContent"
|
||||
view="AddTask.html"
|
||||
view="AddLoopsConcept.html"
|
||||
/>
|
||||
|
||||
<editform
|
||||
label="Edit Task"
|
||||
label="Edit Concept"
|
||||
name="edit.html"
|
||||
schema="loops.interfaces.ITask"
|
||||
for="loops.interfaces.ITask"
|
||||
schema="loops.interfaces.IConcept"
|
||||
for="loops.interfaces.IConcept"
|
||||
permission="zope.ManageContent"
|
||||
menu="zmi_views" title="Edit"
|
||||
/>
|
||||
|
||||
<containerViews
|
||||
for="loops.interfaces.ITask"
|
||||
index="zope.View"
|
||||
contents="zope.View"
|
||||
add="zope.ManageContent"
|
||||
/>
|
||||
|
||||
<defaultView
|
||||
for="loops.interfaces.ITask"
|
||||
for="loops.interfaces.IConcept"
|
||||
name="details.html"
|
||||
/>
|
||||
|
||||
<page
|
||||
name="details.html"
|
||||
for="loops.interfaces.ITask"
|
||||
class=".task.Details"
|
||||
template="task_details.pt"
|
||||
for="loops.interfaces.IConcept"
|
||||
class=".concept.Details"
|
||||
template="concept_details.pt"
|
||||
permission="zope.View"
|
||||
menu="zmi_views" title="Details"
|
||||
/>
|
||||
|
||||
<pages
|
||||
for="loops.interfaces.ITask"
|
||||
class=".task.SubtaskAssignments"
|
||||
for="loops.interfaces.IConcept"
|
||||
class=".concept.ConceptRelations"
|
||||
permission="zope.ManageContent"
|
||||
>
|
||||
|
||||
<page
|
||||
name="subtasks.html"
|
||||
template="subtasks_assign.pt"
|
||||
menu="zmi_views" title="Subtasks"
|
||||
name="assign.html"
|
||||
template="concept_assign.pt"
|
||||
menu="zmi_views" title="Assign Concept"
|
||||
/>
|
||||
|
||||
<page
|
||||
name="subtask_assign"
|
||||
attribute="assignSubtask"
|
||||
name="concept_assign"
|
||||
attribute="assignConcept"
|
||||
/>
|
||||
|
||||
</pages>
|
||||
|
|
206
concept.py
206
concept.py
|
@ -17,193 +17,67 @@
|
|||
#
|
||||
|
||||
"""
|
||||
Definition of the Task class.
|
||||
Definition of the Concept class.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.interface import implements
|
||||
from zope.app.container.ordered import OrderedContainer
|
||||
from zope.app.copypastemove import ObjectCopier
|
||||
from zope.app import zapi
|
||||
from zope.schema.fieldproperty import FieldProperty
|
||||
from zope.component.interfaces import IFactory
|
||||
from zope.interface import implements
|
||||
from persistent import Persistent
|
||||
|
||||
from cybertools.relation.interfaces import IRelationsRegistry
|
||||
from cybertools.relation import DyadicRelation
|
||||
|
||||
#from relation import Relation, Relations
|
||||
from resource import Resource
|
||||
from interfaces import ITask
|
||||
|
||||
from copy import copy
|
||||
from interfaces import IConcept
|
||||
|
||||
|
||||
class SubtaskRelation(DyadicRelation):
|
||||
""" Relation of a first task (the parent task) to a second task
|
||||
(the subtask).
|
||||
"""
|
||||
class Concept(Persistent):
|
||||
|
||||
implements(IConcept)
|
||||
|
||||
class Task(OrderedContainer):
|
||||
|
||||
implements(ITask)
|
||||
|
||||
title = u''
|
||||
qualifier = u''
|
||||
priority = FieldProperty(ITask['priority'])
|
||||
_title = u''
|
||||
def getTitle(self): return self._title
|
||||
def setTitle(self, title): self._title = title
|
||||
title = property(getTitle, setTitle)
|
||||
|
||||
def __init__(self, name=None, title=u''):
|
||||
OrderedContainer.__init__(self)
|
||||
if name:
|
||||
self.__name__ = name
|
||||
if title:
|
||||
self.title = title
|
||||
self.title = title
|
||||
|
||||
# subtasks:
|
||||
# concept relations:
|
||||
|
||||
def getSubtasks(self, taskTypes=None):
|
||||
def getSubConcepts(self, relationships=None):
|
||||
rels = getRelations(first=self, relationships=relationships)
|
||||
return [r.second for r in rels]
|
||||
# TODO: sort...
|
||||
|
||||
def getParentConcepts(self, relationships=None):
|
||||
rels = getRelations(second=self, relationships=relationships)
|
||||
return [r.first for r in rels]
|
||||
|
||||
def assignConcept(self, concept, relationship):
|
||||
registry = zapi.getUtility(IRelationsRegistry)
|
||||
return [r.second
|
||||
for r in registry.query(relationship=SubtaskRelation,
|
||||
first=self)]
|
||||
# TODO: sort according to priority
|
||||
|
||||
def getParentTasks(self, taskTypes=None):
|
||||
registry = zapi.getUtility(IRelationsRegistry)
|
||||
return [r.first
|
||||
for r in registry.query(relationship=SubtaskRelation,
|
||||
second=self)]
|
||||
|
||||
def assignSubtask(self, task):
|
||||
registry = zapi.getUtility(IRelationsRegistry)
|
||||
registry.register(SubtaskRelation(self, task))
|
||||
registry.register(relationship(self, concept))
|
||||
# TODO (?): avoid duplicates
|
||||
|
||||
def createSubtask(self, taskType=None, container=None, name=None):
|
||||
container = container or zapi.getParent(self)
|
||||
task = Task()
|
||||
name = name or task._createTaskName(container)
|
||||
container[name] = task
|
||||
self.assignSubtask(task)
|
||||
return task
|
||||
def deassignConcept(self, concept, relationships=None):
|
||||
pass # TODO
|
||||
|
||||
def deassignSubtask(self, task):
|
||||
hits = [ r for r in self._subtasks if r._target == task ]
|
||||
if hits:
|
||||
self._subtasks.remove(hits[0])
|
||||
task._parentTasks.remove(hits[0])
|
||||
|
||||
# resource allocations:
|
||||
# TODO: move this to the relation package
|
||||
|
||||
def getAllocatedResources(self, allocTypes=None, resTypes=None):
|
||||
from sets import Set
|
||||
allocs = self._resourceAllocs
|
||||
res = Set()
|
||||
for at in allocTypes or allocs.keys():
|
||||
res.union_update(allocs[at])
|
||||
if resTypes:
|
||||
res = [ r for r in res if r in resTypes ]
|
||||
return tuple(res)
|
||||
def getRelations(first=None, second=None, third=None, relationships=None):
|
||||
registry = zapi.getUtility(IRelationsRegistry)
|
||||
query = {}
|
||||
if first: query['first'] = first
|
||||
if second: query['second'] = second
|
||||
if third: query['third'] = third
|
||||
if not relationships:
|
||||
return registry.query(**query)
|
||||
else:
|
||||
result = []
|
||||
for r in relationships:
|
||||
query['relationship'] = r
|
||||
result.extend(registry.query(**query))
|
||||
return result
|
||||
|
||||
def allocateResource(self, resource, allocType='standard'):
|
||||
allocs = self._resourceAllocs
|
||||
rList = allocs.get(allocType, [])
|
||||
if resource not in rList:
|
||||
rList.append(resource)
|
||||
allocs[allocType] = rList
|
||||
resource._addAllocation(self, allocType)
|
||||
|
||||
def createAndAllocateResource(self, resourceType=None, allocType='standard',
|
||||
container=None, name=None):
|
||||
container = container or zapi.getParent(self)
|
||||
rClass = resourceType or Resource
|
||||
resource = rClass()
|
||||
name = name or resource._createResourceName(container)
|
||||
container[name] = resource
|
||||
self.allocateResource(resource, allocType)
|
||||
return resource
|
||||
|
||||
def deallocateResource(self, resource, allocTypes=None):
|
||||
allocs = self._resourceAllocs
|
||||
for at in allocTypes or allocs.keys():
|
||||
if resource in allocs[at]:
|
||||
allocs[at].remove(resource)
|
||||
resource._removeAllocations(self, allocTypes)
|
||||
|
||||
def allocatedUserIds(self):
|
||||
return ()
|
||||
|
||||
def getAllocTypes(self, resource):
|
||||
return ('standard',)
|
||||
|
||||
def getAllAllocTypes(self):
|
||||
return ('standard',)
|
||||
|
||||
# resource constraint stuff:
|
||||
|
||||
def isResourceAllowed(self, resource, rcDontCheck=None):
|
||||
rc = self.resourceConstraints
|
||||
if not rc:
|
||||
return True
|
||||
for c in rc:
|
||||
if rcDontCheck and c == rcDontCheck: # don't check constraint already checked
|
||||
continue
|
||||
# that's too simple, we must check all constraints for constraintType:
|
||||
if not c.isResourceAllowed(resource):
|
||||
return False
|
||||
return True
|
||||
|
||||
def getCandidateResources(self):
|
||||
rc = self.resourceConstraints
|
||||
if not rc:
|
||||
return ()
|
||||
for c in rc:
|
||||
candidates = c.getAllowedResources()
|
||||
if candidates is not None:
|
||||
return tuple([ r for r in candidates if self.isResourceAllowed(r, c) ])
|
||||
return ()
|
||||
|
||||
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([ r for r in candidates if self.isResourceAllowed(r) ])
|
||||
|
||||
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):
|
||||
targetContainer = targetContainer or zapi.getParent(self)
|
||||
newName = self._createTaskName(targetContainer)
|
||||
newTask = copy(self)
|
||||
targetContainer[newName] = newTask
|
||||
subtasks = self.getSubtasks()
|
||||
newTask._subtasks.clear()
|
||||
for st in subtasks:
|
||||
newSt = st.copyTask(targetContainer)
|
||||
newSt._parentTasks.clear()
|
||||
newTask.assignSubtask(newSt)
|
||||
return newTask
|
||||
|
||||
# Helper methods:
|
||||
|
||||
def _createTaskName(self, container=None):
|
||||
prefix = 'tsk'
|
||||
container = container or zapi.getParent(self)
|
||||
last = max([ int(n[len(prefix):]) for n in container.keys() ] or [0])
|
||||
return prefix + str(last+1)
|
||||
|
|
|
@ -10,22 +10,19 @@
|
|||
<!-- Content declarations -->
|
||||
|
||||
<interface
|
||||
interface=".interfaces.ITask"
|
||||
interface=".interfaces.IConcept"
|
||||
type="zope.app.content.interfaces.IContentType" />
|
||||
|
||||
<content class=".task.Task">
|
||||
<content class=".concept.Concept">
|
||||
|
||||
<implements interface="zope.app.container.interfaces.IContentContainer" />
|
||||
|
||||
<implements
|
||||
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
|
||||
|
||||
<implements
|
||||
interface="zope.app.workflow.interfaces.IProcessInstanceContainerAdaptable" />
|
||||
|
||||
<factory
|
||||
id="loops.Task"
|
||||
description="Task object" />
|
||||
id="loops.Concept"
|
||||
description="Concept object" />
|
||||
|
||||
<!-- <require
|
||||
permission="zope.View"
|
||||
|
@ -33,11 +30,11 @@
|
|||
|
||||
<require
|
||||
permission="zope.View"
|
||||
interface=".interfaces.ITask" />
|
||||
interface=".interfaces.IConcept" />
|
||||
|
||||
<require
|
||||
permission="zope.ManageContent"
|
||||
set_schema=".interfaces.ITask" />
|
||||
set_schema=".interfaces.IConcept" />
|
||||
|
||||
</content>
|
||||
|
||||
|
|
255
interfaces.py
255
interfaces.py
|
@ -23,251 +23,42 @@ $Id$
|
|||
"""
|
||||
|
||||
from zope.interface import Interface
|
||||
from zope.app.container.interfaces import IOrderedContainer
|
||||
from zope.i18nmessageid import MessageFactory
|
||||
from zope import schema
|
||||
|
||||
from zope.schema import Text, TextLine, List, Object, Int
|
||||
_ = MessageFactory('loops')
|
||||
|
||||
|
||||
class IRelations(Interface):
|
||||
""" Holds a set of relations (more precisely: ends of relations).
|
||||
A simple implementation is to just use an IOSet.
|
||||
class IConcept(Interface):
|
||||
""" The 'concept' is the central element of the loops framework.
|
||||
A concept is related to other concepts.
|
||||
"""
|
||||
|
||||
def add(relation):
|
||||
""" Add a relation.
|
||||
relationClass is the class that should be used for the relation
|
||||
object; it should have a default setting, e.g. Relation.
|
||||
"""
|
||||
|
||||
def remove(relation):
|
||||
"""
|
||||
"""
|
||||
|
||||
|
||||
class IRelation(Interface):
|
||||
""" Represents a relation from one object to another.
|
||||
"""
|
||||
source = Object(Interface,
|
||||
title=u'Source Object',
|
||||
description=u"Object that is the source of the relation")
|
||||
target = Object(Interface,
|
||||
title=u'Target Object',
|
||||
description=u"Object that is the target of the relation")
|
||||
|
||||
|
||||
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: require, allow, disallow',
|
||||
default=u'require',
|
||||
required=True)
|
||||
|
||||
referenceType = TextLine(
|
||||
title=u'Reference Type',
|
||||
description=u'Type of reference to the resource attribute to check: '
|
||||
'explicit, parent, type, selectmethod, checkmethod.',
|
||||
default=u'explicit',
|
||||
required=True)
|
||||
|
||||
referenceKey = TextLine(
|
||||
title=u'Reference Key',
|
||||
description=u'Key for referencing the resource attribute, '
|
||||
'e.g. method or type name')
|
||||
|
||||
referenceValues = List(
|
||||
title=u'Reference Values',
|
||||
description=u'Values to check for; usually a list of references to '
|
||||
'the objects to be selected (referenceType=explicit) '
|
||||
'or the parent objects (referenceType=parent)',
|
||||
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):
|
||||
""" A Task is a piece of work.
|
||||
|
||||
Resources may be allocated to a Task.
|
||||
A Task may depend on subtasks.
|
||||
"""
|
||||
|
||||
title = TextLine(
|
||||
title=u'Title',
|
||||
description=u'Name or short title of the task',
|
||||
title = schema.TextLine(
|
||||
title=_(u'Title'),
|
||||
description=_(u'Name or short title of the concept'),
|
||||
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 '
|
||||
'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):
|
||||
""" Return a tuple of subtasks of self,
|
||||
possibly restricted to the task types given.
|
||||
def getSubConcepts(relationships=None):
|
||||
""" Return a tuple of concepts related to self as sub-concepts,
|
||||
possibly restricted to the relationships (typically a list of
|
||||
relation classes) given.
|
||||
"""
|
||||
|
||||
def assignSubtask(task):
|
||||
""" Assign an existing task to self as a subtask..
|
||||
def getParentConcepts(relationships=None):
|
||||
""" Return a tuple of concepts related to self as parent concepts,
|
||||
possibly restricted to the relationships (typically a list of
|
||||
relation classes) given.
|
||||
"""
|
||||
|
||||
def getParentTasks():
|
||||
""" Return a tuple of tasks to which self has a subtask relationship.
|
||||
def assignConcept(concept, relationship):
|
||||
""" Assign an existing concept to self using the relationship given.
|
||||
The assigned concept will be a sub-concept of self.
|
||||
"""
|
||||
|
||||
def createSubtask(taskType=None, container=None, name=None):
|
||||
""" Create a new task and assign it to self as a subtask.
|
||||
|
||||
taskType is a class that implements the ITask interface and defaults to Task.
|
||||
The container in which the object will be created defaults to parent of self.
|
||||
The name of the object to be created will be generated if not given.
|
||||
Return the new subtask.
|
||||
"""
|
||||
|
||||
def deassignSubtask(task):
|
||||
""" Remove the subtask relation to task from self.
|
||||
"""
|
||||
|
||||
# resource allocations:
|
||||
|
||||
def getAllocatedResources(allocTypes=None, resTypes=None):
|
||||
""" Return a tuple of resources allocated to self,
|
||||
possibly restricted to the allocation types and
|
||||
target resource types given.
|
||||
"""
|
||||
|
||||
def allocateResource(resource, allocType='standard'):
|
||||
""" Allocate resource to self. A special allocation type may be given.
|
||||
"""
|
||||
|
||||
def createAndAllocateResource(resourceType=None, allocType='standard',
|
||||
container=None, name=None):
|
||||
""" Create resource and allocate it to self.
|
||||
|
||||
resourceType is a class that implements the IResource interface
|
||||
and defaults to Resource.
|
||||
allocType defaults to 'standard'.
|
||||
The container in which the object will be created defaults to parent of self.
|
||||
The name of the object to be created will be generated if not given.
|
||||
Return the new resource object.
|
||||
"""
|
||||
|
||||
def deallocateResource(resource, allocTypes=None):
|
||||
""" Deallocate the resource given from self. If no allocTypes
|
||||
given all allocations to resource will be removed.
|
||||
"""
|
||||
|
||||
def allocatedUserIds():
|
||||
""" Returns tuple of user IDs of allocated Person objects that are portal members.
|
||||
Used by catalog index 'allocUserIds'.
|
||||
"""
|
||||
|
||||
def getAllocTypes(resource):
|
||||
""" Return the allocation types with which the resource given
|
||||
is allocated to self.
|
||||
"""
|
||||
|
||||
def getAllAllocTypes():
|
||||
""" Return a tuple with all available allocation types defined
|
||||
in the controller object that is responsible for self.
|
||||
"""
|
||||
|
||||
# resource constraint stuff:
|
||||
|
||||
def isResourceAllowed(resource, rcDontCheck=None):
|
||||
""" Return True if the resource given is allowed for this task.
|
||||
|
||||
If rcDontChedk is given this resource constraint will be skipped
|
||||
during checking.
|
||||
"""
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
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):
|
||||
""" 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):
|
||||
""" 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.
|
||||
def deassignConcept(concept, relationships=None):
|
||||
""" Remove the relations to the concept given from self, optionally
|
||||
restricting them to the relationships given.
|
||||
"""
|
||||
|
||||
|
|
9
tests.py
9
tests.py
|
@ -8,16 +8,15 @@ from zope.interface import implements
|
|||
from zope.app import zapi
|
||||
from zope.app.intid.interfaces import IIntIds
|
||||
|
||||
from interfaces import ITask
|
||||
from task import Task
|
||||
from interfaces import IConcept
|
||||
from concept import Concept
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
"Basic tests for the loops package."
|
||||
|
||||
def testInterfaces(self):
|
||||
verifyClass(ITask, Task)
|
||||
self.assert_(ITask.providedBy(Task()),
|
||||
'Interface ITask is not implemented by class Task.')
|
||||
verifyClass(IConcept, Concept)
|
||||
self.assert_(IConcept.providedBy(Concept()))
|
||||
|
||||
|
||||
def test_suite():
|
||||
|
|
Loading…
Add table
Reference in a new issue