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$)
|
($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
|
>>> from loops.concept import Concept
|
||||||
>>> t1 = Task()
|
>>> c1 = Concept()
|
||||||
>>> t1.title
|
>>> c1.title
|
||||||
u''
|
u''
|
||||||
|
|
||||||
>>> t2 = Task(u't2', u'Second Task')
|
>>> c2 = Concept(u'c2', u'Second Concept')
|
||||||
>>> t2.title
|
>>> c2.title
|
||||||
u'Second Task'
|
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
|
In order to do this we first have to provide a relations registry. For
|
||||||
testing we use a simple dummy implementation.
|
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.interfaces import IRelationsRegistry
|
||||||
>>> from cybertools.relation.registry import DummyRelationsRegistry
|
>>> from cybertools.relation.registry import DummyRelationsRegistry
|
||||||
>>> from zope.app.testing import ztapi
|
>>> from zope.app.testing import ztapi
|
||||||
|
|
||||||
>>> ztapi.provideUtility(IRelationsRegistry, DummyRelationsRegistry())
|
>>> 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()
|
>>> sc1 = c1.getSubConcepts()
|
||||||
>>> len(st1)
|
>>> len(sc1)
|
||||||
1
|
1
|
||||||
>>> t2 in st1
|
>>> c2 in sc1
|
||||||
True
|
True
|
||||||
>>> len(t1.getParentTasks())
|
>>> len(c1.getParentConcepts())
|
||||||
|
0
|
||||||
|
|
||||||
|
>>> pc2 = c2.getParentConcepts()
|
||||||
|
>>> len(pc2)
|
||||||
|
1
|
||||||
|
|
||||||
|
>>> c1 in pc2
|
||||||
|
True
|
||||||
|
>>> len(c2.getSubConcepts())
|
||||||
0
|
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.app.dublincore.interfaces import ICMFDublinCore
|
||||||
from zope.security.proxy import removeSecurityProxy
|
from zope.security.proxy import removeSecurityProxy
|
||||||
|
|
||||||
from loops.interfaces import ITask
|
from loops.interfaces import IConcept
|
||||||
|
|
||||||
class Details(object):
|
class Details(object):
|
||||||
|
|
||||||
|
@ -38,15 +38,15 @@ class Details(object):
|
||||||
return d and d.strftime('%Y-%m-%d %H:%M') or ''
|
return d and d.strftime('%Y-%m-%d %H:%M') or ''
|
||||||
|
|
||||||
|
|
||||||
class SubtaskAssignments(Details):
|
class ConceptRelations(Details):
|
||||||
|
|
||||||
def assignSubtask(self):
|
def assignSubtask(self):
|
||||||
""" Add a subtask denoted by the path given in the
|
""" Add a subtask denoted by the path given in the
|
||||||
request variable subtaskPath.
|
request variable subtaskPath.
|
||||||
"""
|
"""
|
||||||
subtaskPath = self.request.get('subtaskPath')
|
conceptName = self.request.get('concept_name')
|
||||||
#if subtaskPath:
|
#if conceptName:
|
||||||
subtask = zapi.traverse(zapi.getRoot(self.context), subtaskPath, None, self.request)
|
concept = zapi.getParent(self.context)[conceptName]
|
||||||
#if subtask:
|
#if concept:
|
||||||
self.context.assignSubtask(removeSecurityProxy(subtask))
|
self.context.assignConcept(removeSecurityProxy(concept))
|
||||||
self.request.response.redirect('.')
|
self.request.response.redirect('.')
|
||||||
|
|
|
@ -6,24 +6,24 @@
|
||||||
<body>
|
<body>
|
||||||
<div metal:fill-slot="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">
|
<div class="row">
|
||||||
<span class="label">Subtasks</span>:
|
<span class="label">Sub-Concepts</span>:
|
||||||
<span class="field"
|
<span class="field"
|
||||||
tal:repeat="task context/getSubtasks">
|
tal:repeat="concept context/getSubConcepts">
|
||||||
<span tal:condition="python: task is None">**deleted**</span>
|
<span tal:condition="python: concept is None">**deleted**</span>
|
||||||
<span tal:condition="python: task is not None"
|
<span tal:condition="python: concept is not None"
|
||||||
tal:content="task/title">subtask</span>
|
tal:content="concept/title">subconcept</span>
|
||||||
<span class="field" tal:condition="not:repeat/task/end"> - </span>
|
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="label">Parent Tasks</span>:
|
<span class="label">Parent Concepts</span>:
|
||||||
<span class="field"
|
<span class="field"
|
||||||
tal:repeat="task context/getParentTasks">
|
tal:repeat="concept context/getParentConcepts">
|
||||||
<span tal:content="task/title">parent task</span>
|
<span tal:content="concept/title">parent concept</span>
|
||||||
<span class="field" tal:condition="not:repeat/task/end"> - </span>
|
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -34,13 +34,13 @@
|
||||||
<form action="." method="post"
|
<form action="." method="post"
|
||||||
tal:attributes="action context/@@absolute_url">
|
tal:attributes="action context/@@absolute_url">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="label">Subtasks</span>:
|
<span class="label">Concept Name</span>:
|
||||||
<span class="field">
|
<span class="field">
|
||||||
<input type="test" name="subtaskPath" />
|
<input type="test" name="concept_name" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input type="submit" name="subtask_assign:method" value="Assign Subtask" />
|
<input type="submit" name="concept_assign:method" value="Assign Concept" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -6,24 +6,24 @@
|
||||||
<body>
|
<body>
|
||||||
<div metal:fill-slot="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">
|
<div class="row">
|
||||||
<span class="label">Subtasks</span>:
|
<span class="label">Sub-Concepts</span>:
|
||||||
<span class="field"
|
<span class="field"
|
||||||
tal:repeat="task context/getSubtasks">
|
tal:repeat="concept context/getSubConcepts">
|
||||||
<span tal:condition="python: task is None">**deleted**</span>
|
<span tal:condition="python: concept is None">**deleted**</span>
|
||||||
<span tal:condition="python: task is not None"
|
<span tal:condition="python: concept is not None"
|
||||||
tal:content="task/title">subtask</span>
|
tal:content="concept/title">subtask</span>
|
||||||
<span class="field" tal:condition="not:repeat/task/end"> - </span>
|
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="label">Parent Tasks</span>:
|
<span class="label">Parent Concepts</span>:
|
||||||
<span class="field"
|
<span class="field"
|
||||||
tal:repeat="task context/getParentTasks">
|
tal:repeat="concept context/getParentConcepts">
|
||||||
<span tal:content="task/title">parent task</span>
|
<span tal:content="concept/title">parent concept</span>
|
||||||
<span class="field" tal:condition="not:repeat/task/end"> - </span>
|
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
@ -7,67 +7,60 @@
|
||||||
>
|
>
|
||||||
|
|
||||||
<addform
|
<addform
|
||||||
label="Add Task"
|
label="Add Concept"
|
||||||
name="AddTask.html"
|
name="AddLoopsConcept.html"
|
||||||
schema="loops.interfaces.ITask"
|
schema="loops.interfaces.IConcept"
|
||||||
content_factory="loops.task.Task"
|
content_factory="loops.concept.Concept"
|
||||||
fields="title"
|
fields="title"
|
||||||
permission="zope.ManageContent"
|
permission="zope.ManageContent"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<addMenuItem
|
<addMenuItem
|
||||||
class="loops.task.Task"
|
class="loops.concept.Concept"
|
||||||
title="Task"
|
title="Concept"
|
||||||
description="A Task is a piece of work or something else a resource may be allocated to"
|
description="A Concept is a Concept is a Concept..."
|
||||||
permission="zope.ManageContent"
|
permission="zope.ManageContent"
|
||||||
view="AddTask.html"
|
view="AddLoopsConcept.html"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<editform
|
<editform
|
||||||
label="Edit Task"
|
label="Edit Concept"
|
||||||
name="edit.html"
|
name="edit.html"
|
||||||
schema="loops.interfaces.ITask"
|
schema="loops.interfaces.IConcept"
|
||||||
for="loops.interfaces.ITask"
|
for="loops.interfaces.IConcept"
|
||||||
permission="zope.ManageContent"
|
permission="zope.ManageContent"
|
||||||
menu="zmi_views" title="Edit"
|
menu="zmi_views" title="Edit"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<containerViews
|
|
||||||
for="loops.interfaces.ITask"
|
|
||||||
index="zope.View"
|
|
||||||
contents="zope.View"
|
|
||||||
add="zope.ManageContent"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<defaultView
|
<defaultView
|
||||||
for="loops.interfaces.ITask"
|
for="loops.interfaces.IConcept"
|
||||||
name="details.html"
|
name="details.html"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<page
|
<page
|
||||||
name="details.html"
|
name="details.html"
|
||||||
for="loops.interfaces.ITask"
|
for="loops.interfaces.IConcept"
|
||||||
class=".task.Details"
|
class=".concept.Details"
|
||||||
template="task_details.pt"
|
template="concept_details.pt"
|
||||||
permission="zope.View"
|
permission="zope.View"
|
||||||
menu="zmi_views" title="Details"
|
menu="zmi_views" title="Details"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<pages
|
<pages
|
||||||
for="loops.interfaces.ITask"
|
for="loops.interfaces.IConcept"
|
||||||
class=".task.SubtaskAssignments"
|
class=".concept.ConceptRelations"
|
||||||
permission="zope.ManageContent"
|
permission="zope.ManageContent"
|
||||||
>
|
>
|
||||||
|
|
||||||
<page
|
<page
|
||||||
name="subtasks.html"
|
name="assign.html"
|
||||||
template="subtasks_assign.pt"
|
template="concept_assign.pt"
|
||||||
menu="zmi_views" title="Subtasks"
|
menu="zmi_views" title="Assign Concept"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<page
|
<page
|
||||||
name="subtask_assign"
|
name="concept_assign"
|
||||||
attribute="assignSubtask"
|
attribute="assignConcept"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</pages>
|
</pages>
|
||||||
|
|
206
concept.py
206
concept.py
|
@ -17,193 +17,67 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Definition of the Task class.
|
Definition of the Concept class.
|
||||||
|
|
||||||
$Id$
|
$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.app import zapi
|
||||||
from zope.schema.fieldproperty import FieldProperty
|
from zope.interface import implements
|
||||||
from zope.component.interfaces import IFactory
|
from persistent import Persistent
|
||||||
|
|
||||||
from cybertools.relation.interfaces import IRelationsRegistry
|
from cybertools.relation.interfaces import IRelationsRegistry
|
||||||
from cybertools.relation import DyadicRelation
|
from cybertools.relation import DyadicRelation
|
||||||
|
|
||||||
#from relation import Relation, Relations
|
from interfaces import IConcept
|
||||||
from resource import Resource
|
|
||||||
from interfaces import ITask
|
|
||||||
|
|
||||||
from copy import copy
|
|
||||||
|
|
||||||
|
|
||||||
class SubtaskRelation(DyadicRelation):
|
class Concept(Persistent):
|
||||||
""" Relation of a first task (the parent task) to a second task
|
|
||||||
(the subtask).
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
implements(IConcept)
|
||||||
|
|
||||||
class Task(OrderedContainer):
|
_title = u''
|
||||||
|
def getTitle(self): return self._title
|
||||||
implements(ITask)
|
def setTitle(self, title): self._title = title
|
||||||
|
title = property(getTitle, setTitle)
|
||||||
title = u''
|
|
||||||
qualifier = u''
|
|
||||||
priority = FieldProperty(ITask['priority'])
|
|
||||||
|
|
||||||
def __init__(self, name=None, title=u''):
|
def __init__(self, name=None, title=u''):
|
||||||
OrderedContainer.__init__(self)
|
self.title = title
|
||||||
if name:
|
|
||||||
self.__name__ = name
|
|
||||||
if 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)
|
registry = zapi.getUtility(IRelationsRegistry)
|
||||||
return [r.second
|
registry.register(relationship(self, concept))
|
||||||
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))
|
|
||||||
# TODO (?): avoid duplicates
|
# TODO (?): avoid duplicates
|
||||||
|
|
||||||
def createSubtask(self, taskType=None, container=None, name=None):
|
def deassignConcept(self, concept, relationships=None):
|
||||||
container = container or zapi.getParent(self)
|
pass # TODO
|
||||||
task = Task()
|
|
||||||
name = name or task._createTaskName(container)
|
|
||||||
container[name] = task
|
|
||||||
self.assignSubtask(task)
|
|
||||||
return task
|
|
||||||
|
|
||||||
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):
|
def getRelations(first=None, second=None, third=None, relationships=None):
|
||||||
from sets import Set
|
registry = zapi.getUtility(IRelationsRegistry)
|
||||||
allocs = self._resourceAllocs
|
query = {}
|
||||||
res = Set()
|
if first: query['first'] = first
|
||||||
for at in allocTypes or allocs.keys():
|
if second: query['second'] = second
|
||||||
res.union_update(allocs[at])
|
if third: query['third'] = third
|
||||||
if resTypes:
|
if not relationships:
|
||||||
res = [ r for r in res if r in resTypes ]
|
return registry.query(**query)
|
||||||
return tuple(res)
|
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 -->
|
<!-- Content declarations -->
|
||||||
|
|
||||||
<interface
|
<interface
|
||||||
interface=".interfaces.ITask"
|
interface=".interfaces.IConcept"
|
||||||
type="zope.app.content.interfaces.IContentType" />
|
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.container.interfaces.IContentContainer" />
|
||||||
|
|
||||||
<implements
|
<implements
|
||||||
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
|
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
|
||||||
|
|
||||||
<implements
|
|
||||||
interface="zope.app.workflow.interfaces.IProcessInstanceContainerAdaptable" />
|
|
||||||
|
|
||||||
<factory
|
<factory
|
||||||
id="loops.Task"
|
id="loops.Concept"
|
||||||
description="Task object" />
|
description="Concept object" />
|
||||||
|
|
||||||
<!-- <require
|
<!-- <require
|
||||||
permission="zope.View"
|
permission="zope.View"
|
||||||
|
@ -33,11 +30,11 @@
|
||||||
|
|
||||||
<require
|
<require
|
||||||
permission="zope.View"
|
permission="zope.View"
|
||||||
interface=".interfaces.ITask" />
|
interface=".interfaces.IConcept" />
|
||||||
|
|
||||||
<require
|
<require
|
||||||
permission="zope.ManageContent"
|
permission="zope.ManageContent"
|
||||||
set_schema=".interfaces.ITask" />
|
set_schema=".interfaces.IConcept" />
|
||||||
|
|
||||||
</content>
|
</content>
|
||||||
|
|
||||||
|
|
255
interfaces.py
255
interfaces.py
|
@ -23,251 +23,42 @@ $Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.interface import Interface
|
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):
|
class IConcept(Interface):
|
||||||
""" Holds a set of relations (more precisely: ends of relations).
|
""" The 'concept' is the central element of the loops framework.
|
||||||
A simple implementation is to just use an IOSet.
|
A concept is related to other concepts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def add(relation):
|
title = schema.TextLine(
|
||||||
""" Add a relation.
|
title=_(u'Title'),
|
||||||
relationClass is the class that should be used for the relation
|
description=_(u'Name or short title of the concept'),
|
||||||
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',
|
|
||||||
default=u'',
|
default=u'',
|
||||||
required=True)
|
required=True)
|
||||||
|
|
||||||
qualifier = TextLine(
|
def getSubConcepts(relationships=None):
|
||||||
title=u'Qualifier',
|
""" Return a tuple of concepts related to self as sub-concepts,
|
||||||
description=u'Some string telling more specifically what this task is about',
|
possibly restricted to the relationships (typically a list of
|
||||||
default=u'',
|
relation classes) given.
|
||||||
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 assignSubtask(task):
|
def getParentConcepts(relationships=None):
|
||||||
""" Assign an existing task to self as a subtask..
|
""" 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():
|
def assignConcept(concept, relationship):
|
||||||
""" Return a tuple of tasks to which self has a subtask 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):
|
def deassignConcept(concept, relationships=None):
|
||||||
""" Create a new task and assign it to self as a subtask.
|
""" Remove the relations to the concept given from self, optionally
|
||||||
|
restricting them to the relationships given.
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
9
tests.py
9
tests.py
|
@ -8,16 +8,15 @@ from zope.interface import implements
|
||||||
from zope.app import zapi
|
from zope.app import zapi
|
||||||
from zope.app.intid.interfaces import IIntIds
|
from zope.app.intid.interfaces import IIntIds
|
||||||
|
|
||||||
from interfaces import ITask
|
from interfaces import IConcept
|
||||||
from task import Task
|
from concept import Concept
|
||||||
|
|
||||||
class Test(unittest.TestCase):
|
class Test(unittest.TestCase):
|
||||||
"Basic tests for the loops package."
|
"Basic tests for the loops package."
|
||||||
|
|
||||||
def testInterfaces(self):
|
def testInterfaces(self):
|
||||||
verifyClass(ITask, Task)
|
verifyClass(IConcept, Concept)
|
||||||
self.assert_(ITask.providedBy(Task()),
|
self.assert_(IConcept.providedBy(Concept()))
|
||||||
'Interface ITask is not implemented by class Task.')
|
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
|
|
Loading…
Add table
Reference in a new issue