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
148
README.txt
148
README.txt
|
@ -1,64 +1,126 @@
|
|||
loops - Linked Objects for Organizational Process Services
|
||||
==========================================================
|
||||
===============================================================
|
||||
loops - Linked Objects for Organization and Processing Services
|
||||
===============================================================
|
||||
|
||||
($Id$)
|
||||
|
||||
Concepts and Relations between them
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Concepts and Relations
|
||||
======================
|
||||
|
||||
Let's start with creating a few example concepts, putting them in a
|
||||
top-level loops container and a concept manager:
|
||||
|
||||
>>> from loops import Loops
|
||||
>>> loops = Loops()
|
||||
|
||||
>>> from loops.concept import ConceptManager, Concept
|
||||
>>> loops['concepts'] = ConceptManager()
|
||||
>>> concepts = loops['concepts']
|
||||
>>> c1 = Concept()
|
||||
>>> concepts['c1'] = c1
|
||||
>>> c1.title
|
||||
u''
|
||||
>>> from loops import Loops
|
||||
>>> loops = Loops()
|
||||
|
||||
>>> c2 = Concept(u'c2', u'Second Concept')
|
||||
>>> concepts['c2'] = c2
|
||||
>>> c2.title
|
||||
u'Second Concept'
|
||||
>>> from loops.concept import ConceptManager, Concept
|
||||
>>> loops['concepts'] = ConceptManager()
|
||||
>>> concepts = loops['concepts']
|
||||
>>> zope = Concept()
|
||||
>>> concepts['zope'] = zope
|
||||
>>> zope.title
|
||||
u''
|
||||
|
||||
>>> zope3 = Concept(u'Zope 3')
|
||||
>>> concepts['zope3'] = zope3
|
||||
>>> zope3.title
|
||||
u'Zope 3'
|
||||
|
||||
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.
|
||||
|
||||
>>> from cybertools.relation.interfaces import IRelationsRegistry
|
||||
>>> from cybertools.relation.registry import DummyRelationsRegistry
|
||||
>>> from zope.app.testing import ztapi
|
||||
>>> ztapi.provideUtility(IRelationsRegistry, DummyRelationsRegistry())
|
||||
>>> from cybertools.relation.interfaces import IRelationsRegistry
|
||||
>>> from cybertools.relation.registry import DummyRelationsRegistry
|
||||
>>> from zope.app.testing import ztapi
|
||||
>>> ztapi.provideUtility(IRelationsRegistry, DummyRelationsRegistry())
|
||||
|
||||
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:
|
||||
Now we can assign the concept c2 to c1 (using the standard ConceptRelation):
|
||||
|
||||
>>> c1.assignConcept(c2)
|
||||
>>> zope.assignConcept(zope3)
|
||||
|
||||
We can now ask our concepts for their related concepts:
|
||||
|
||||
>>> sc1 = c1.getSubConcepts()
|
||||
>>> len(sc1)
|
||||
1
|
||||
>>> c2 in sc1
|
||||
True
|
||||
>>> len(c1.getParentConcepts())
|
||||
0
|
||||
>>> sc1 = zope.getSubConcepts()
|
||||
>>> len(sc1)
|
||||
1
|
||||
>>> zope3 in sc1
|
||||
True
|
||||
>>> len(zope.getParentConcepts())
|
||||
0
|
||||
|
||||
>>> pc2 = c2.getParentConcepts()
|
||||
>>> len(pc2)
|
||||
1
|
||||
|
||||
>>> c1 in pc2
|
||||
True
|
||||
>>> len(c2.getSubConcepts())
|
||||
0
|
||||
>>> pc2 = zope3.getParentConcepts()
|
||||
>>> len(pc2)
|
||||
1
|
||||
|
||||
>>> zope in pc2
|
||||
True
|
||||
>>> len(zope3.getSubConcepts())
|
||||
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
|
||||
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):
|
||||
|
||||
|
@ -45,6 +57,6 @@ class ConceptRelations(Details):
|
|||
""" Assign a concept denoted by the 'concept_name' request parameter.
|
||||
"""
|
||||
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))
|
||||
|
||||
|
|
|
@ -11,18 +11,20 @@
|
|||
<div class="row">
|
||||
<span class="label">Sub-Concepts</span>:
|
||||
<span class="field"
|
||||
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>
|
||||
tal:repeat="concept view/subConcepts">
|
||||
<a href="#"
|
||||
tal:attributes="href concept/url"
|
||||
tal:content="concept/title">subtask</a>
|
||||
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">Parent Concepts</span>:
|
||||
<span class="field"
|
||||
tal:repeat="concept context/getParentConcepts">
|
||||
<span tal:content="concept/title">parent concept</span>
|
||||
tal:repeat="concept view/parentConcepts">
|
||||
<a href="#"
|
||||
tal:attributes="href concept/url"
|
||||
tal:content="concept/title">subtask</a>
|
||||
<span class="field" tal:condition="not:repeat/concept/end"> - </span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
60
concept.py
60
concept.py
|
@ -28,13 +28,13 @@ from zope.app.container.contained import Contained
|
|||
from zope.interface import implements
|
||||
from persistent import Persistent
|
||||
|
||||
from cybertools.relation.interfaces import IRelationsRegistry
|
||||
from cybertools.relation import DyadicRelation
|
||||
from cybertools.relation.registry import IRelationsRegistry, getRelations
|
||||
|
||||
from interfaces import IConcept
|
||||
from interfaces import IConceptManager, IConceptManagerContained
|
||||
from interfaces import ILoopsContained
|
||||
from relations import ConceptRelation
|
||||
from relations import ConceptRelation, ConceptResourceRelation
|
||||
|
||||
|
||||
class Concept(Contained, Persistent):
|
||||
|
@ -46,48 +46,60 @@ class Concept(Contained, Persistent):
|
|||
def setTitle(self, title): self._title = title
|
||||
title = property(getTitle, setTitle)
|
||||
|
||||
def __init__(self, name=None, title=u''):
|
||||
def __init__(self, title=u''):
|
||||
self.title = title
|
||||
|
||||
# concept relations:
|
||||
# concept relations
|
||||
|
||||
def getSubConcepts(self, relationships=None):
|
||||
if relationships is None:
|
||||
relationships = [ConceptRelation]
|
||||
rels = getRelations(first=self, relationships=relationships)
|
||||
return [r.second for r in rels]
|
||||
# TODO: sort...
|
||||
|
||||
def getParentConcepts(self, relationships=None):
|
||||
if relationships is None:
|
||||
relationships = [ConceptRelation]
|
||||
rels = getRelations(second=self, relationships=relationships)
|
||||
return [r.first for r in rels]
|
||||
|
||||
def assignConcept(self, concept, relationship=ConceptRelation):
|
||||
registry = zapi.getUtility(IRelationsRegistry)
|
||||
registry.register(relationship(self, concept))
|
||||
rel = relationship(self, concept)
|
||||
registry.register(rel)
|
||||
# TODO (?): avoid duplicates
|
||||
|
||||
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):
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
relation classes) given.
|
||||
"""
|
||||
|
@ -63,6 +63,8 @@ class IConcept(Interface):
|
|||
def assignConcept(concept, relationship):
|
||||
""" Assign an existing concept to self using the relationship given.
|
||||
The assigned concept will be a sub-concept of self.
|
||||
|
||||
The relationship defaults to ConceptRelation.
|
||||
"""
|
||||
|
||||
def deassignConcept(concept, relationships=None):
|
||||
|
@ -70,6 +72,23 @@ class IConcept(Interface):
|
|||
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):
|
||||
""" A manager/container for concepts.
|
||||
"""
|
||||
|
@ -92,6 +111,11 @@ class IResource(Interface):
|
|||
description=_(u'Title of the document'),
|
||||
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):
|
||||
""" A resource containing an editable body.
|
||||
|
@ -166,10 +190,6 @@ class INodeContained(Interface):
|
|||
containers(INode, IViewManager)
|
||||
|
||||
|
||||
class IViewManagerContained(Interface):
|
||||
containers(IViewManager)
|
||||
|
||||
|
||||
# the loops top-level container
|
||||
|
||||
class ILoops(IFolder):
|
||||
|
|
13
relations.py
13
relations.py
|
@ -33,12 +33,17 @@ class ConceptRelation(DyadicRelation):
|
|||
"""
|
||||
|
||||
|
||||
class ResourceRelation(DyadicRelation):
|
||||
""" A relation between concept and resource objects.
|
||||
class ConceptResourceRelation(DyadicRelation):
|
||||
""" A relation between a concept and a resource object.
|
||||
"""
|
||||
|
||||
|
||||
class ViewRelation(DyadicRelation):
|
||||
""" A relation between view and concept objects.
|
||||
class ViewConceptRelation(DyadicRelation):
|
||||
""" 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.interface import implements
|
||||
from persistent import Persistent
|
||||
from cybertools.relation.registry import getRelations
|
||||
|
||||
from interfaces import IResource, IDocument
|
||||
from interfaces import IResourceManager, IResourceManagerContained
|
||||
|
@ -42,9 +43,13 @@ class Resource(Contained, Persistent):
|
|||
def setTitle(self, title): self._title = title
|
||||
title = property(getTitle, setTitle)
|
||||
|
||||
def __init__(self, name=None, title=u''):
|
||||
def __init__(self, title=u''):
|
||||
self.title = title
|
||||
|
||||
def getClients(self, relationships=None):
|
||||
rels = getRelations(second=self, relationships=relationships)
|
||||
return [r.first for r in rels]
|
||||
|
||||
|
||||
class Document(Resource):
|
||||
|
||||
|
|
5
tests.py
5
tests.py
|
@ -1,6 +1,6 @@
|
|||
# $Id$
|
||||
|
||||
import unittest
|
||||
import unittest, doctest
|
||||
from zope.testing.doctestunit import DocFileSuite
|
||||
from zope.app.testing import ztapi
|
||||
from zope.interface.verify import verifyClass
|
||||
|
@ -37,9 +37,10 @@ class Test(unittest.TestCase):
|
|||
|
||||
|
||||
def test_suite():
|
||||
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(Test),
|
||||
DocFileSuite('README.txt'),
|
||||
DocFileSuite('README.txt', optionflags=flags),
|
||||
))
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
9
view.py
9
view.py
|
@ -30,13 +30,13 @@ from zope.interface import implements
|
|||
from persistent import Persistent
|
||||
|
||||
from interfaces import IView, INode
|
||||
from interfaces import IViewManager, IViewManagerContained
|
||||
from interfaces import IViewManager, INodeContained
|
||||
from interfaces import ILoopsContained
|
||||
|
||||
|
||||
class View(object):
|
||||
|
||||
implements(IView, IViewManagerContained)
|
||||
implements(IView, INodeContained)
|
||||
|
||||
_title = u''
|
||||
def getTitle(self): return self._title
|
||||
|
@ -48,15 +48,16 @@ class View(object):
|
|||
def setDescription(self, description): self._description = description
|
||||
description = property(getDescription, setDescription)
|
||||
|
||||
def __init__(self, name=None, title=u''):
|
||||
def __init__(self, title=u'', description=u''):
|
||||
self.title = title
|
||||
self.description = description
|
||||
super(View, self).__init__()
|
||||
|
||||
def getConcepts(self):
|
||||
return []
|
||||
|
||||
|
||||
class Node(OrderedContainer, View):
|
||||
class Node(View, OrderedContainer):
|
||||
|
||||
implements(INode)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue