Work in progress: concepts, resources, views...
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@848 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
b9dfa307d1
commit
0ab81ca2e0
9 changed files with 210 additions and 90 deletions
108
README.txt
108
README.txt
|
@ -1,10 +1,11 @@
|
||||||
loops - Linked Objects for Organizational Process Services
|
===============================================================
|
||||||
==========================================================
|
loops - Linked Objects for Organization and Processing Services
|
||||||
|
===============================================================
|
||||||
|
|
||||||
($Id$)
|
($Id$)
|
||||||
|
|
||||||
Concepts and Relations between them
|
Concepts and Relations
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
======================
|
||||||
|
|
||||||
Let's start with creating a few example concepts, putting them in a
|
Let's start with creating a few example concepts, putting them in a
|
||||||
top-level loops container and a concept manager:
|
top-level loops container and a concept manager:
|
||||||
|
@ -15,15 +16,15 @@ top-level loops container and a concept manager:
|
||||||
>>> from loops.concept import ConceptManager, Concept
|
>>> from loops.concept import ConceptManager, Concept
|
||||||
>>> loops['concepts'] = ConceptManager()
|
>>> loops['concepts'] = ConceptManager()
|
||||||
>>> concepts = loops['concepts']
|
>>> concepts = loops['concepts']
|
||||||
>>> c1 = Concept()
|
>>> zope = Concept()
|
||||||
>>> concepts['c1'] = c1
|
>>> concepts['zope'] = zope
|
||||||
>>> c1.title
|
>>> zope.title
|
||||||
u''
|
u''
|
||||||
|
|
||||||
>>> c2 = Concept(u'c2', u'Second Concept')
|
>>> zope3 = Concept(u'Zope 3')
|
||||||
>>> concepts['c2'] = c2
|
>>> concepts['zope3'] = zope3
|
||||||
>>> c2.title
|
>>> zope3.title
|
||||||
u'Second Concept'
|
u'Zope 3'
|
||||||
|
|
||||||
Now we want to relate the second concept to the first one.
|
Now we want to relate the second concept to the first one.
|
||||||
|
|
||||||
|
@ -35,30 +36,91 @@ testing we use a simple dummy implementation.
|
||||||
>>> from zope.app.testing import ztapi
|
>>> from zope.app.testing import ztapi
|
||||||
>>> ztapi.provideUtility(IRelationsRegistry, DummyRelationsRegistry())
|
>>> ztapi.provideUtility(IRelationsRegistry, DummyRelationsRegistry())
|
||||||
|
|
||||||
We also need a Relation class to be used for connecting concepts:
|
Now we can assign the concept c2 to c1 (using the standard ConceptRelation):
|
||||||
|
|
||||||
>>> from cybertools.relation import DyadicRelation
|
>>> zope.assignConcept(zope3)
|
||||||
|
|
||||||
Now we can assign the concept c2 to c1:
|
|
||||||
|
|
||||||
>>> c1.assignConcept(c2)
|
|
||||||
|
|
||||||
We can now ask our concepts for their related concepts:
|
We can now ask our concepts for their related concepts:
|
||||||
|
|
||||||
>>> sc1 = c1.getSubConcepts()
|
>>> sc1 = zope.getSubConcepts()
|
||||||
>>> len(sc1)
|
>>> len(sc1)
|
||||||
1
|
1
|
||||||
>>> c2 in sc1
|
>>> zope3 in sc1
|
||||||
True
|
True
|
||||||
>>> len(c1.getParentConcepts())
|
>>> len(zope.getParentConcepts())
|
||||||
0
|
0
|
||||||
|
|
||||||
>>> pc2 = c2.getParentConcepts()
|
>>> pc2 = zope3.getParentConcepts()
|
||||||
>>> len(pc2)
|
>>> len(pc2)
|
||||||
1
|
1
|
||||||
|
|
||||||
>>> c1 in pc2
|
>>> zope in pc2
|
||||||
True
|
True
|
||||||
>>> len(c2.getSubConcepts())
|
>>> len(zope3.getSubConcepts())
|
||||||
0
|
0
|
||||||
|
|
||||||
|
TODO: Work with views...
|
||||||
|
|
||||||
|
Resources and what they have to do with Concepts
|
||||||
|
================================================
|
||||||
|
|
||||||
|
We first need a resource manager:
|
||||||
|
|
||||||
|
>>> from loops.resource import ResourceManager, Document
|
||||||
|
>>> loops['resources'] = ResourceManager()
|
||||||
|
>>> resources = loops['resources']
|
||||||
|
|
||||||
|
A common type of resource is a Document:
|
||||||
|
|
||||||
|
>>> zope_info = Document(u'Zope Info')
|
||||||
|
>>> resources['zope_info'] = zope_info
|
||||||
|
>>> zope_info.title
|
||||||
|
u'Zope Info'
|
||||||
|
>>> zope_info.body
|
||||||
|
u''
|
||||||
|
>>> zope_info.format
|
||||||
|
u'text/xml'
|
||||||
|
|
||||||
|
We can associate a resource with a concept by assigning it to the concept:
|
||||||
|
|
||||||
|
>>> zope.assignResource(zope_info)
|
||||||
|
>>> res = zope.getResources()
|
||||||
|
>>> list(res)
|
||||||
|
[<loops.resource.Document ...>]
|
||||||
|
|
||||||
|
The resource also provides access to the associated concepts (or views, see
|
||||||
|
below):
|
||||||
|
|
||||||
|
>>> conc = zope_info.getClients()
|
||||||
|
>>> len(conc)
|
||||||
|
1
|
||||||
|
>>> conc[0] is zope
|
||||||
|
True
|
||||||
|
|
||||||
|
Views: Menus, Menu Items, Listings, etc
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
We first need a view manager:
|
||||||
|
|
||||||
|
>>> from loops.view import ViewManager, Node
|
||||||
|
>>> loops['views'] = ViewManager()
|
||||||
|
>>> views = loops['views']
|
||||||
|
|
||||||
|
The view space is typically built up with nodes; a node may be a top-level
|
||||||
|
menu that may contain other nodes as menu items:
|
||||||
|
|
||||||
|
>>> m1 = Node(u'Menu')
|
||||||
|
>>> views['m1'] = m1
|
||||||
|
>>> m11 = Node(u'Zope')
|
||||||
|
>>> m1['m11'] = m11
|
||||||
|
>>> m111 = Node(u'Zope in General')
|
||||||
|
>>> m11['m111'] = m111
|
||||||
|
>>> m112 = Node(u'Zope 3')
|
||||||
|
>>> m11['m112'] = m112
|
||||||
|
>>> m112.title
|
||||||
|
u'Zope 3'
|
||||||
|
>>> m112.description
|
||||||
|
u''
|
||||||
|
|
||||||
|
We can associate a node with a concept or directly with a resource:
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,18 @@ class Details(object):
|
||||||
d = dc.modified or dc.created
|
d = dc.modified or dc.created
|
||||||
return d and d.strftime('%Y-%m-%d %H:%M') or ''
|
return d and d.strftime('%Y-%m-%d %H:%M') or ''
|
||||||
|
|
||||||
|
def subConcepts(self):
|
||||||
|
return [{'object': c,
|
||||||
|
'title': c.title,
|
||||||
|
'url': zapi.absoluteURL(c, self.request)}
|
||||||
|
for c in self.context.getSubConcepts()]
|
||||||
|
|
||||||
|
def parentConcepts(self):
|
||||||
|
return [{'object': c,
|
||||||
|
'title': c.title,
|
||||||
|
'url': zapi.absoluteURL(c, self.request)}
|
||||||
|
for c in self.context.getParentConcepts()]
|
||||||
|
|
||||||
|
|
||||||
class ConceptRelations(Details):
|
class ConceptRelations(Details):
|
||||||
|
|
||||||
|
@ -45,6 +57,6 @@ class ConceptRelations(Details):
|
||||||
""" Assign a concept denoted by the 'concept_name' request parameter.
|
""" Assign a concept denoted by the 'concept_name' request parameter.
|
||||||
"""
|
"""
|
||||||
concept = zapi.getParent(self.context)[concept_name]
|
concept = zapi.getParent(self.context)[concept_name]
|
||||||
self.context.assignConcept(removeSecurityProxy(concept), DyadicRelation)
|
self.context.assignConcept(removeSecurityProxy(concept))
|
||||||
self.request.response.redirect(zapi.absoluteURL(self.context, self.request))
|
self.request.response.redirect(zapi.absoluteURL(self.context, self.request))
|
||||||
|
|
||||||
|
|
|
@ -11,18 +11,20 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="label">Sub-Concepts</span>:
|
<span class="label">Sub-Concepts</span>:
|
||||||
<span class="field"
|
<span class="field"
|
||||||
tal:repeat="concept context/getSubConcepts">
|
tal:repeat="concept view/subConcepts">
|
||||||
<span tal:condition="python: concept is None">**deleted**</span>
|
<a href="#"
|
||||||
<span tal:condition="python: concept is not None"
|
tal:attributes="href concept/url"
|
||||||
tal:content="concept/title">subtask</span>
|
tal:content="concept/title">subtask</a>
|
||||||
<span class="field" tal:condition="not:repeat/concept/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 Concepts</span>:
|
<span class="label">Parent Concepts</span>:
|
||||||
<span class="field"
|
<span class="field"
|
||||||
tal:repeat="concept context/getParentConcepts">
|
tal:repeat="concept view/parentConcepts">
|
||||||
<span tal:content="concept/title">parent concept</span>
|
<a href="#"
|
||||||
|
tal:attributes="href concept/url"
|
||||||
|
tal:content="concept/title">subtask</a>
|
||||||
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
60
concept.py
60
concept.py
|
@ -28,13 +28,13 @@ from zope.app.container.contained import Contained
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
from persistent import Persistent
|
from persistent import Persistent
|
||||||
|
|
||||||
from cybertools.relation.interfaces import IRelationsRegistry
|
|
||||||
from cybertools.relation import DyadicRelation
|
from cybertools.relation import DyadicRelation
|
||||||
|
from cybertools.relation.registry import IRelationsRegistry, getRelations
|
||||||
|
|
||||||
from interfaces import IConcept
|
from interfaces import IConcept
|
||||||
from interfaces import IConceptManager, IConceptManagerContained
|
from interfaces import IConceptManager, IConceptManagerContained
|
||||||
from interfaces import ILoopsContained
|
from interfaces import ILoopsContained
|
||||||
from relations import ConceptRelation
|
from relations import ConceptRelation, ConceptResourceRelation
|
||||||
|
|
||||||
|
|
||||||
class Concept(Contained, Persistent):
|
class Concept(Contained, Persistent):
|
||||||
|
@ -46,48 +46,60 @@ class Concept(Contained, Persistent):
|
||||||
def setTitle(self, title): self._title = title
|
def setTitle(self, title): self._title = title
|
||||||
title = property(getTitle, setTitle)
|
title = property(getTitle, setTitle)
|
||||||
|
|
||||||
def __init__(self, name=None, title=u''):
|
def __init__(self, title=u''):
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
||||||
# concept relations:
|
# concept relations
|
||||||
|
|
||||||
def getSubConcepts(self, relationships=None):
|
def getSubConcepts(self, relationships=None):
|
||||||
|
if relationships is None:
|
||||||
|
relationships = [ConceptRelation]
|
||||||
rels = getRelations(first=self, relationships=relationships)
|
rels = getRelations(first=self, relationships=relationships)
|
||||||
return [r.second for r in rels]
|
return [r.second for r in rels]
|
||||||
# TODO: sort...
|
# TODO: sort...
|
||||||
|
|
||||||
def getParentConcepts(self, relationships=None):
|
def getParentConcepts(self, relationships=None):
|
||||||
|
if relationships is None:
|
||||||
|
relationships = [ConceptRelation]
|
||||||
rels = getRelations(second=self, relationships=relationships)
|
rels = getRelations(second=self, relationships=relationships)
|
||||||
return [r.first for r in rels]
|
return [r.first for r in rels]
|
||||||
|
|
||||||
def assignConcept(self, concept, relationship=ConceptRelation):
|
def assignConcept(self, concept, relationship=ConceptRelation):
|
||||||
registry = zapi.getUtility(IRelationsRegistry)
|
registry = zapi.getUtility(IRelationsRegistry)
|
||||||
registry.register(relationship(self, concept))
|
rel = relationship(self, concept)
|
||||||
|
registry.register(rel)
|
||||||
# TODO (?): avoid duplicates
|
# TODO (?): avoid duplicates
|
||||||
|
|
||||||
def deassignConcept(self, concept, relationships=None):
|
def deassignConcept(self, concept, relationships=None):
|
||||||
pass # TODO
|
registry = zapi.getUtility(IRelationsRegistry)
|
||||||
|
relations = registry.query(first=self, second=concept,
|
||||||
|
relationships=relationships)
|
||||||
|
for rel in relations:
|
||||||
|
registry.unregister(relation)
|
||||||
|
|
||||||
|
# resource relations
|
||||||
|
|
||||||
|
def getResources(self, relationships=None):
|
||||||
|
if relationships is None:
|
||||||
|
relationships = [ConceptResourceRelation]
|
||||||
|
rels = getRelations(first=self, relationships=relationships)
|
||||||
|
return [r.second for r in rels]
|
||||||
|
# TODO: sort...
|
||||||
|
|
||||||
|
def assignResource(self, resource, relationship=ConceptResourceRelation):
|
||||||
|
registry = zapi.getUtility(IRelationsRegistry)
|
||||||
|
registry.register(relationship(self, resource))
|
||||||
|
# TODO (?): avoid duplicates
|
||||||
|
|
||||||
|
def deassignResource(self, resource, relationships=None):
|
||||||
|
registry = zapi.getUtility(IRelationsRegistry)
|
||||||
|
relations = registry.query(first=self, second=resource,
|
||||||
|
relationships=relationships)
|
||||||
|
for rel in relations:
|
||||||
|
registry.unregister(relation)
|
||||||
|
|
||||||
|
|
||||||
class ConceptManager(BTreeContainer):
|
class ConceptManager(BTreeContainer):
|
||||||
|
|
||||||
implements(IConceptManager, ILoopsContained)
|
implements(IConceptManager, ILoopsContained)
|
||||||
|
|
||||||
|
|
||||||
# TODO: move this to the cybertools.relation package
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ class IConcept(Interface):
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
def getSubConcepts(relationships=None):
|
def getSubConcepts(relationships=None):
|
||||||
""" Return a tuple of concepts related to self as sub-concepts,
|
""" Return a sequence of concepts related to self as sub-concepts,
|
||||||
possibly restricted to the relationships (typically a list of
|
possibly restricted to the relationships (typically a list of
|
||||||
relation classes) given.
|
relation classes) given.
|
||||||
"""
|
"""
|
||||||
|
@ -63,6 +63,8 @@ class IConcept(Interface):
|
||||||
def assignConcept(concept, relationship):
|
def assignConcept(concept, relationship):
|
||||||
""" Assign an existing concept to self using the relationship given.
|
""" Assign an existing concept to self using the relationship given.
|
||||||
The assigned concept will be a sub-concept of self.
|
The assigned concept will be a sub-concept of self.
|
||||||
|
|
||||||
|
The relationship defaults to ConceptRelation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def deassignConcept(concept, relationships=None):
|
def deassignConcept(concept, relationships=None):
|
||||||
|
@ -70,6 +72,23 @@ class IConcept(Interface):
|
||||||
restricting them to the relationships given.
|
restricting them to the relationships given.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def getResources(relationships=None):
|
||||||
|
""" Return a sequence of resources assigned to self,
|
||||||
|
possibly restricted to the relationships given.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def assignResource(resource, relationship):
|
||||||
|
""" Assign an existing resource to self using the relationship given.
|
||||||
|
|
||||||
|
The relationship defaults to ConceptResourceRelation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def deassignResource(resource, relationships=None):
|
||||||
|
""" Remove the relations to the resource given from self, optionally
|
||||||
|
restricting them to the relationships given.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class IConceptManager(IContainer):
|
class IConceptManager(IContainer):
|
||||||
""" A manager/container for concepts.
|
""" A manager/container for concepts.
|
||||||
"""
|
"""
|
||||||
|
@ -92,6 +111,11 @@ class IResource(Interface):
|
||||||
description=_(u'Title of the document'),
|
description=_(u'Title of the document'),
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
|
def getClients(relationships=None):
|
||||||
|
""" Return a sequence of objects that are clients of the resource,
|
||||||
|
i.e. that have some relation with it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class IDocument(IResource):
|
class IDocument(IResource):
|
||||||
""" A resource containing an editable body.
|
""" A resource containing an editable body.
|
||||||
|
@ -166,10 +190,6 @@ class INodeContained(Interface):
|
||||||
containers(INode, IViewManager)
|
containers(INode, IViewManager)
|
||||||
|
|
||||||
|
|
||||||
class IViewManagerContained(Interface):
|
|
||||||
containers(IViewManager)
|
|
||||||
|
|
||||||
|
|
||||||
# the loops top-level container
|
# the loops top-level container
|
||||||
|
|
||||||
class ILoops(IFolder):
|
class ILoops(IFolder):
|
||||||
|
|
13
relations.py
13
relations.py
|
@ -33,12 +33,17 @@ class ConceptRelation(DyadicRelation):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ResourceRelation(DyadicRelation):
|
class ConceptResourceRelation(DyadicRelation):
|
||||||
""" A relation between concept and resource objects.
|
""" A relation between a concept and a resource object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ViewRelation(DyadicRelation):
|
class ViewConceptRelation(DyadicRelation):
|
||||||
""" A relation between view and concept objects.
|
""" A relation between a view and a concept object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ViewResourceRelation(DyadicRelation):
|
||||||
|
""" A relation between a view and a resource object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ from zope.app.container.btree import BTreeContainer
|
||||||
from zope.app.container.contained import Contained
|
from zope.app.container.contained import Contained
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
from persistent import Persistent
|
from persistent import Persistent
|
||||||
|
from cybertools.relation.registry import getRelations
|
||||||
|
|
||||||
from interfaces import IResource, IDocument
|
from interfaces import IResource, IDocument
|
||||||
from interfaces import IResourceManager, IResourceManagerContained
|
from interfaces import IResourceManager, IResourceManagerContained
|
||||||
|
@ -42,9 +43,13 @@ class Resource(Contained, Persistent):
|
||||||
def setTitle(self, title): self._title = title
|
def setTitle(self, title): self._title = title
|
||||||
title = property(getTitle, setTitle)
|
title = property(getTitle, setTitle)
|
||||||
|
|
||||||
def __init__(self, name=None, title=u''):
|
def __init__(self, title=u''):
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
||||||
|
def getClients(self, relationships=None):
|
||||||
|
rels = getRelations(second=self, relationships=relationships)
|
||||||
|
return [r.first for r in rels]
|
||||||
|
|
||||||
|
|
||||||
class Document(Resource):
|
class Document(Resource):
|
||||||
|
|
||||||
|
|
5
tests.py
5
tests.py
|
@ -1,6 +1,6 @@
|
||||||
# $Id$
|
# $Id$
|
||||||
|
|
||||||
import unittest
|
import unittest, doctest
|
||||||
from zope.testing.doctestunit import DocFileSuite
|
from zope.testing.doctestunit import DocFileSuite
|
||||||
from zope.app.testing import ztapi
|
from zope.app.testing import ztapi
|
||||||
from zope.interface.verify import verifyClass
|
from zope.interface.verify import verifyClass
|
||||||
|
@ -37,9 +37,10 @@ class Test(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
|
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||||
return unittest.TestSuite((
|
return unittest.TestSuite((
|
||||||
unittest.makeSuite(Test),
|
unittest.makeSuite(Test),
|
||||||
DocFileSuite('README.txt'),
|
DocFileSuite('README.txt', optionflags=flags),
|
||||||
))
|
))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
9
view.py
9
view.py
|
@ -30,13 +30,13 @@ from zope.interface import implements
|
||||||
from persistent import Persistent
|
from persistent import Persistent
|
||||||
|
|
||||||
from interfaces import IView, INode
|
from interfaces import IView, INode
|
||||||
from interfaces import IViewManager, IViewManagerContained
|
from interfaces import IViewManager, INodeContained
|
||||||
from interfaces import ILoopsContained
|
from interfaces import ILoopsContained
|
||||||
|
|
||||||
|
|
||||||
class View(object):
|
class View(object):
|
||||||
|
|
||||||
implements(IView, IViewManagerContained)
|
implements(IView, INodeContained)
|
||||||
|
|
||||||
_title = u''
|
_title = u''
|
||||||
def getTitle(self): return self._title
|
def getTitle(self): return self._title
|
||||||
|
@ -48,15 +48,16 @@ class View(object):
|
||||||
def setDescription(self, description): self._description = description
|
def setDescription(self, description): self._description = description
|
||||||
description = property(getDescription, setDescription)
|
description = property(getDescription, setDescription)
|
||||||
|
|
||||||
def __init__(self, name=None, title=u''):
|
def __init__(self, title=u'', description=u''):
|
||||||
self.title = title
|
self.title = title
|
||||||
self.description = description
|
self.description = description
|
||||||
|
super(View, self).__init__()
|
||||||
|
|
||||||
def getConcepts(self):
|
def getConcepts(self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
class Node(OrderedContainer, View):
|
class Node(View, OrderedContainer):
|
||||||
|
|
||||||
implements(INode)
|
implements(INode)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue