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:
helmutm 2005-12-06 16:27:17 +00:00
parent b9dfa307d1
commit 0ab81ca2e0
9 changed files with 210 additions and 90 deletions

View file

@ -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:

View file

@ -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))

View file

@ -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>

View file

@ -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

View file

@ -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):

View file

@ -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.
"""

View file

@ -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):

View file

@ -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__':

View file

@ -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)