diff --git a/README.txt b/README.txt
index f9c43b0..f0e70b4 100755
--- a/README.txt
+++ b/README.txt
@@ -41,12 +41,15 @@ top-level loops container and a concept manager:
Now we want to relate the second concept to the first one.
In order to do this we first have to provide a relation registry. For
-testing we use a simple dummy implementation.
+testing we use a simple dummy implementation. As relationships are
+based on predicates that are themselves concepts we also need a
+default predicate concept; the default name for this is 'standard'.
>>> from cybertools.relation.interfaces import IRelationRegistry
>>> from cybertools.relation.registry import DummyRelationRegistry
>>> from zope.app.testing import ztapi
>>> ztapi.provideUtility(IRelationRegistry, DummyRelationRegistry())
+ >>> concepts['standard'] = Concept('Default Predicate')
Now we can assign the concept c2 as a child to c1 (using the standard
ConceptRelation):
@@ -74,8 +77,10 @@ We can now ask our concepts for their related child and parent concepts:
Each concept should have a concept type; this is in fact provided by a
relation to a special kind of concept object with the magic name 'type'.
-This type object is its own type:
+This type object is its own type. The type relations themselves are of
+a special predicate 'hasType'.
+ >>> concepts['hasType'] = Concept(u'Type Predicate')
>>> concepts['type'] = Concept(u'Type')
>>> typeObject = concepts['type']
>>> typeObject.setConceptType(typeObject)
@@ -99,6 +104,13 @@ This type object is its own type:
>>> cc1.getConceptType().title
u'Topic'
+We get a list of types using the ConceptTypeSourceList:
+
+ >>> from loops.concept import ConceptTypeSourceList
+ >>> types = ConceptTypeSourceList(cc1)
+ >>> sorted(t.title for t in types)
+ [u'Topic', u'Type', u'Unknown Type']
+
Concept Views
-------------
@@ -108,9 +120,6 @@ Concept Views
>>> sorted([c.title for c in view.children()])
[u'Zope 3']
-The concept view allows to get a list of terms (sort of vocabulary) that
-can be used to show the objects in a listing:
-
The concept view allows updating the underlying context object:
>>> cc3 = Concept(u'loops for Zope 3')
@@ -388,9 +397,9 @@ objects.) The source is basically a source list:
>>> from loops.target import TargetSourceList
>>> source = TargetSourceList(m111)
>>> len(source)
- 8
+ 1
>>> sorted([zapi.getName(s) for s in source])
- [u'cc1', u'cc2', u'cc3', u'cc4', u'doc1', u'topic', u'type', u'unknown']
+ [u'doc1']
The form then uses a sort of browser view providing the ITerms interface
based on this source list:
diff --git a/browser/common.py b/browser/common.py
index 91a8858..67de75b 100644
--- a/browser/common.py
+++ b/browser/common.py
@@ -27,7 +27,6 @@ from zope.app.dublincore.interfaces import ICMFDublinCore
from zope.app.form.browser.interfaces import ITerms
from zope.cachedescriptors.property import Lazy
from zope.interface import implements
-#from zope.schema.vocabulary import SimpleTerm
from zope.security.proxy import removeSecurityProxy
class BaseView(object):
@@ -85,9 +84,6 @@ class LoopsTerms(object):
def getTerm(self, value):
return BaseView(value, self.request)
- #token = self.loopsRoot.getLoopsUri(value)
- #title = value.title or zapi.getName(value)
- #return SimpleTerm(value, token, title)
def getValue(self, token):
return self.loopsRoot.loopsTraverse(token)
diff --git a/browser/concept.py b/browser/concept.py
index f179387..86f8912 100644
--- a/browser/concept.py
+++ b/browser/concept.py
@@ -25,8 +25,10 @@ $Id$
from zope.app import zapi
from zope.app.catalog.interfaces import ICatalog
from zope.app.dublincore.interfaces import ICMFDublinCore
+from zope.app.event.objectevent import ObjectCreatedEvent
from zope.app.form.browser.interfaces import ITerms
from zope.cachedescriptors.property import Lazy
+from zope.event import notify
from zope.interface import implements
from zope.publisher.interfaces import BadRequest
from zope import schema
@@ -90,6 +92,8 @@ class ConceptView(BaseView):
concept = Concept(title)
container = self.loopsRoot.getConceptManager()
container[name] = concept
+ # TODO: notify ObjectCreatedEvent() (?)
+ #notify(ObjectCreatedEvent(removeSecurityProxy(concept)))
assignAs = self.request.get('assignAs', 'child')
if assignAs == 'child':
self.context.assignChild(removeSecurityProxy(concept))
diff --git a/concept.py b/concept.py
index 4280d76..fb6584e 100644
--- a/concept.py
+++ b/concept.py
@@ -35,7 +35,7 @@ from persistent import Persistent
from cybertools.relation import DyadicRelation
from cybertools.relation.registry import getRelations
from cybertools.relation.registry import getRelationSingle, setRelationSingle
-from cybertools.relation.interfaces import IRelationRegistry
+from cybertools.relation.interfaces import IRelationRegistry, IRelatable
from interfaces import IConcept, IConceptRelation, IConceptView
from interfaces import IConceptManager, IConceptManagerContained
@@ -50,12 +50,18 @@ class ConceptRelation(DyadicRelation):
"""
implements(IConceptRelation)
+ def __init__(self, first, second, predicate=None):
+ super(ConceptRelation, self).__init__(first, second)
+ if predicate is None:
+ context = first is not None and first or second
+ cm = context.getLoopsRoot().getConceptManager()
+ predicate = cm.getDefaultPredicate()
+ self.predicate = predicate
-class TypeRelation(DyadicRelation):
- """ A special relation between two concepts, the parent specifying
- the type of the child.
- """
- implements(IConceptRelation)
+ def getPredicateName(self):
+ baseName = super(ConceptRelation, self).getPredicateName()
+ id = zapi.getUtility(IRelationRegistry).getUniqueIdForObject(self.predicate)
+ return '.'.join((baseName, str(id)))
class ResourceRelation(DyadicRelation):
@@ -68,7 +74,7 @@ class ResourceRelation(DyadicRelation):
class Concept(Contained, Persistent):
- implements(IConcept, IConceptManagerContained)
+ implements(IConcept, IConceptManagerContained, IRelatable)
proxyInterface = IConceptView
@@ -78,11 +84,16 @@ class Concept(Contained, Persistent):
title = property(getTitle, setTitle)
def getConceptType(self):
- rel = getRelationSingle(self, TypeRelation)
- return rel and rel.second or None
+ cm = self.getLoopsRoot().getConceptManager()
+ typeRelation = ConceptRelation(None, self, cm.getTypePredicate())
+ rel = getRelationSingle(self, typeRelation, forSecond=True)
+ return rel and rel.first or None
def setConceptType(self, concept):
if self.getConceptType() != concept:
- setRelationSingle(TypeRelation(self, removeSecurityProxy(concept)))
+ cm = self.getLoopsRoot().getConceptManager()
+ typeRelation = ConceptRelation(removeSecurityProxy(concept), self,
+ cm.getTypePredicate())
+ setRelationSingle(typeRelation, forSecond=True)
conceptType = property(getConceptType, setConceptType)
def __init__(self, title=u''):
@@ -95,14 +106,14 @@ class Concept(Contained, Persistent):
def getChildren(self, relationships=None):
if relationships is None:
- relationships = [ConceptRelation]
+ relationships = [ConceptRelation(self, None)]
rels = getRelations(first=self, relationships=relationships)
return [r.second for r in rels]
# TODO: sort...
def getParents(self, relationships=None):
if relationships is None:
- relationships = [ConceptRelation]
+ relationships = [ConceptRelation(None, self)]
rels = getRelations(second=self, relationships=relationships)
return [r.first for r in rels]
@@ -117,7 +128,7 @@ class Concept(Contained, Persistent):
def deassignChildren(self, concept, relationships=None):
if relationships is None:
- relationships = [ConceptRelation]
+ relationships = [ConceptRelation(self, None)]
registry = zapi.getUtility(IRelationRegistry)
relations = []
for rs in relationships:
@@ -159,9 +170,28 @@ class ConceptManager(BTreeContainer):
implements(IConceptManager, ILoopsContained)
+ typeConcept = None
+ typePredicate = None
+ defaultPredicate = None
+
def getLoopsRoot(self):
return zapi.getParent(self)
+ def getTypePredicate(self):
+ if self.typePredicate is None:
+ self.typePredicate = self['hasType']
+ return self.typePredicate
+
+ def getTypeConcept(self):
+ if self.typeConcept is None:
+ self.typeConcept = self['type']
+ return self.typeConcept
+
+ def getDefaultPredicate(self):
+ if self.defaultPredicate is None:
+ self.defaultPredicate = self['standard']
+ return self.defaultPredicate
+
def getViewManager(self):
return self.getLoopsRoot().getViewManager()
@@ -202,10 +232,12 @@ class ConceptTypeSourceList(object):
@Lazy
def conceptTypes(self):
result = []
- typeObject = self.concepts.get('type')
+ cm = self.concepts
+ typeObject = cm.getTypeConcept()
unknownType = self.concepts.get('unknown')
if typeObject is not None:
- types = typeObject.getParents((TypeRelation,))
+ typeRelation = ConceptRelation(None, typeObject, cm.getTypePredicate())
+ types = typeObject.getChildren((typeRelation,))
if typeObject not in types:
result.append(typeObject)
if unknownType is not None and unknownType not in types:
diff --git a/configure.zcml b/configure.zcml
index dd98212..0536d19 100644
--- a/configure.zcml
+++ b/configure.zcml
@@ -74,6 +74,10 @@
permission="zope.ManageContent"
interface="zope.app.container.interfaces.IWriteContainer" />
+
+