diff --git a/README.txt b/README.txt
index f0e70b4..11c4551 100755
--- a/README.txt
+++ b/README.txt
@@ -49,7 +49,7 @@ default predicate concept; the default name for this is 'standard'.
>>> from cybertools.relation.registry import DummyRelationRegistry
>>> from zope.app.testing import ztapi
>>> ztapi.provideUtility(IRelationRegistry, DummyRelationRegistry())
- >>> concepts['standard'] = Concept('Default Predicate')
+ >>> concepts['standard'] = Concept('parent')
Now we can assign the concept c2 as a child to c1 (using the standard
ConceptRelation):
@@ -58,20 +58,13 @@ ConceptRelation):
We can now ask our concepts for their related child and parent concepts:
- >>> sc1 = cc1.getChildren()
- >>> len(sc1)
- 1
- >>> cc2 in sc1
- True
+ >>> [zapi.getName(c) for c in cc1.getChildren()]
+ [u'cc2']
>>> len(cc1.getParents())
0
+ >>> [zapi.getName(p) for p in cc2.getParents()]
+ [u'cc1']
- >>> pc2 = cc2.getParents()
- >>> len(pc2)
- 1
-
- >>> cc1 in pc2
- True
>>> len(cc2.getChildren())
0
@@ -80,7 +73,7 @@ relation to a special kind of concept object with the magic name '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['hasType'] = Concept(u'has type')
>>> concepts['type'] = Concept(u'Type')
>>> typeObject = concepts['type']
>>> typeObject.setConceptType(typeObject)
@@ -117,9 +110,18 @@ Concept Views
>>> from loops.browser.concept import ConceptView
>>> view = ConceptView(cc1, TestRequest())
- >>> sorted([c.title for c in view.children()])
+ >>> children = list(view.children())
+ >>> [c.title for c in children]
[u'Zope 3']
+The token attribute provided with the items returned by the children() and
+parents() methods identifies identifies not only the item itself but
+also the relationship to the context object using a combination
+of URIs to item and the predicate of the relationship:
+
+ >>> [c.token for c in children]
+ ['.loops/concepts/cc2:.loops/concepts/standard']
+
The concept view allows updating the underlying context object:
>>> cc3 = Concept(u'loops for Zope 3')
@@ -133,7 +135,7 @@ The concept view allows updating the underlying context object:
>>> view = ConceptView(cc1,
... TestRequest(action='remove', qualifier='children',
- ... tokens=['.loops/concepts/cc2']))
+ ... tokens=['.loops/concepts/cc2:.loops/concepts/standard']))
>>> view.update()
True
>>> sorted(c.title for c in cc1.getChildren())
diff --git a/browser/common.py b/browser/common.py
index 67de75b..aa09e93 100644
--- a/browser/common.py
+++ b/browser/common.py
@@ -32,8 +32,8 @@ from zope.security.proxy import removeSecurityProxy
class BaseView(object):
def __init__(self, context, request):
- self.context = context
- #self.context = removeSecurityProxy(context)
+ #self.context = context
+ self.context = removeSecurityProxy(context)
self.request = request
@Lazy
@@ -64,6 +64,15 @@ class BaseView(object):
def value(self):
return self.context
+ @Lazy
+ def typeTitle(self):
+ return self.context.conceptType.title
+
+ @Lazy
+ def typeUrl(self):
+ return zapi.absoluteURL(self.context.conceptType, self.request)
+
+
class LoopsTerms(object):
""" Provide the ITerms interface, e.g. for usage in selection
diff --git a/browser/concept.py b/browser/concept.py
index 86f8912..c2459ed 100644
--- a/browser/concept.py
+++ b/browser/concept.py
@@ -40,12 +40,22 @@ from loops.browser.common import BaseView, LoopsTerms
class ConceptView(BaseView):
def children(self):
- ch = self.context.getChildren()
- return ch and self.viewIterator(ch) or []
+ for r in self.context.getChildRelations():
+ yield ConceptRelationView(r, self.request, contextIsSecond=True)
def parents(self):
- p = self.context.getParents()
- return p and self.viewIterator(p) or []
+ for r in self.context.getParentRelations():
+ yield ConceptRelationView(r, self.request)
+ #rels = self.context.getParentRelations()
+ #result = []
+ #for r in rels:
+ # p = r.first
+ #if p is None: # this should not be necessary
+ # print 'Warning: parents() got a None first on', \
+ # zapi.getName(self.context), zapi.getName(r.predicate)
+ # continue
+ # p.predicate = r.predicate
+ #return result and self.viewIterator(result) or []
def viewIterator(self, objs):
request = self.request
@@ -61,6 +71,10 @@ class ConceptView(BaseView):
return True
tokens = self.request.get('tokens', [])
for token in tokens:
+ parts = token.split(':')
+ token = parts[0]
+ if len(parts) > 1:
+ relToken = parts[1]
concept = self.loopsRoot.loopsTraverse(token)
if action == 'assign':
assignAs = self.request.get('assignAs', 'child')
@@ -71,11 +85,12 @@ class ConceptView(BaseView):
else:
raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs)
elif action == 'remove':
+ predicate = self.loopsRoot.loopsTraverse(relToken)
qualifier = self.request.get('qualifier')
if qualifier == 'parents':
- self.context.deassignParents(concept)
+ self.context.deassignParents(concept, [predicate])
elif qualifier == 'children':
- self.context.deassignChildren(concept)
+ self.context.deassignChildren(concept, [predicate])
else:
raise(BadRequest, 'Illegal qualifier: %s.' % qualifier)
else:
@@ -102,12 +117,6 @@ class ConceptView(BaseView):
else:
raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs)
- #def getVocabularyForRelated(self): # obsolete
- # source = ConceptSourceList(self.context)
- # terms = zapi.getMultiAdapter((source, self.request), ITerms)
- # for candidate in source:
- # yield terms.getTerm(candidate)
-
def search(self):
request = self.request
if request.get('action') != 'search':
@@ -120,3 +129,50 @@ class ConceptView(BaseView):
result = self.loopsRoot.getConceptManager().values()
return self.viewIterator(result)
+
+class ConceptRelationView(object):
+
+ def __init__(self, relation, request, contextIsSecond=False):
+ if contextIsSecond:
+ self.context = relation.second
+ self.other = relation.first
+ else:
+ self.context = relation.first
+ self.other = relation.second
+ self.predicate = relation.predicate
+ self.conceptType = self.context.conceptType
+ self.request = request
+
+ @Lazy
+ def loopsRoot(self):
+ return self.context.getLoopsRoot()
+
+ @Lazy
+ def url(self):
+ return zapi.absoluteURL(self.context, self.request)
+
+ @Lazy
+ def title(self):
+ return self.context.title
+
+ @Lazy
+ def token(self):
+ return ':'.join((self.loopsRoot.getLoopsUri(self.context),
+ self.loopsRoot.getLoopsUri(self.predicate)))
+
+ @Lazy
+ def typeTitle(self):
+ return self.conceptType.title
+
+ @Lazy
+ def typeUrl(self):
+ return zapi.absoluteURL(self.conceptType, self.request)
+
+ @Lazy
+ def predicateTitle(self):
+ return self.predicate.title
+
+ @Lazy
+ def predicateUrl(self):
+ return zapi.absoluteURL(self.predicate, self.request)
+
diff --git a/browser/relation_macros.pt b/browser/relation_macros.pt
index a116ce8..5924ffb 100644
--- a/browser/relation_macros.pt
+++ b/browser/relation_macros.pt
@@ -22,6 +22,8 @@
|
Title |
+ Predicate |
+ Type |
@@ -37,6 +39,23 @@
Title
+
+
+
+ Type
+
+ |
+
+
+
+ Title
+
+ |
diff --git a/concept.py b/concept.py
index fb6584e..ba9a646 100644
--- a/concept.py
+++ b/concept.py
@@ -45,13 +45,10 @@ from interfaces import ISearchableText
# relation classes
-class ConceptRelation(DyadicRelation):
- """ A relation between concept objects.
- """
- implements(IConceptRelation)
+class BaseRelation(DyadicRelation):
def __init__(self, first, second, predicate=None):
- super(ConceptRelation, self).__init__(first, second)
+ super(BaseRelation, self).__init__(first, second)
if predicate is None:
context = first is not None and first or second
cm = context.getLoopsRoot().getConceptManager()
@@ -59,12 +56,18 @@ class ConceptRelation(DyadicRelation):
self.predicate = predicate
def getPredicateName(self):
- baseName = super(ConceptRelation, self).getPredicateName()
+ baseName = super(BaseRelation, self).getPredicateName()
id = zapi.getUtility(IRelationRegistry).getUniqueIdForObject(self.predicate)
return '.'.join((baseName, str(id)))
-class ResourceRelation(DyadicRelation):
+class ConceptRelation(BaseRelation):
+ """ A relation between concept objects.
+ """
+ implements(IConceptRelation)
+
+
+class ResourceRelation(BaseRelation):
""" A relation between a concept and a resource object.
"""
implements(IConceptRelation)
@@ -84,16 +87,23 @@ class Concept(Contained, Persistent):
title = property(getTitle, setTitle)
def getConceptType(self):
- cm = self.getLoopsRoot().getConceptManager()
- typeRelation = ConceptRelation(None, self, cm.getTypePredicate())
- rel = getRelationSingle(self, typeRelation, forSecond=True)
- return rel and rel.first or None
+ typePred = self.getConceptManager().getTypePredicate()
+ parents = self.getParents([typePred])
+ #typeRelation = ConceptRelation(None, self, cm.getTypePredicate())
+ #rels = getRelationSingle(self, typeRelation, forSecond=True)
+ # TODO (?): check for multiple types (->Error)
+ return parents and parents[0] or None
def setConceptType(self, concept):
- if self.getConceptType() != concept:
- cm = self.getLoopsRoot().getConceptManager()
- typeRelation = ConceptRelation(removeSecurityProxy(concept), self,
- cm.getTypePredicate())
- setRelationSingle(typeRelation, forSecond=True)
+ current = self.getConceptType()
+ if current != concept:
+ typePred = self.getConceptManager().getTypePredicate()
+ if current is not None:
+ self.deassignParents(current, [typePred])
+ self.assignParent(concept, typePred)
+ #cm = self.getLoopsRoot().getConceptManager()
+ #typeRelation = ConceptRelation(removeSecurityProxy(concept), self,
+ # cm.getTypePredicate())
+ #setRelationSingle(typeRelation, forSecond=True)
conceptType = property(getConceptType, setConceptType)
def __init__(self, title=u''):
@@ -102,66 +112,74 @@ class Concept(Contained, Persistent):
def getLoopsRoot(self):
return zapi.getParent(self).getLoopsRoot()
+ def getConceptManager(self):
+ return self.getLoopsRoot().getConceptManager()
+
# concept relations
- def getChildren(self, relationships=None):
- if relationships is None:
- relationships = [ConceptRelation(self, None)]
- rels = getRelations(first=self, relationships=relationships)
- return [r.second for r in rels]
+ def getChildRelations(self, predicates=None, second=None):
+ predicates = predicates is None and ['*'] or predicates
+ relationships = [ConceptRelation(self, None, p) for p in predicates]
# TODO: sort...
+ return getRelations(first=self, second=second, relationships=relationships)
- def getParents(self, relationships=None):
- if relationships is None:
- relationships = [ConceptRelation(None, self)]
- rels = getRelations(second=self, relationships=relationships)
- return [r.first for r in rels]
+ def getChildren(self, predicates=None):
+ return [r.second for r in self.getChildRelations(predicates)]
- def assignChild(self, concept, relationship=ConceptRelation):
+ def getParentRelations (self, predicates=None, first=None):
+ predicates = predicates is None and ['*'] or predicates
+ relationships = [ConceptRelation(None, self, p) for p in predicates]
+ # TODO: sort...
+ return getRelations(first=first, second=self, relationships=relationships)
+
+ def getParents(self, predicates=None):
+ return [r.first for r in self.getParentRelations(predicates)]
+
+ def assignChild(self, concept, predicate=None):
+ if predicate is None:
+ predicate = self.getConceptManager().getDefaultPredicate()
registry = zapi.getUtility(IRelationRegistry)
- rel = relationship(self, concept)
+ rel = ConceptRelation(self, concept, predicate)
registry.register(rel)
# TODO (?): avoid duplicates
- def assignParent(self, concept, relationship=ConceptRelation):
- concept.assignChild(self, relationship)
+ def assignParent(self, concept, predicate=None):
+ concept.assignChild(self, predicate)
- def deassignChildren(self, concept, relationships=None):
- if relationships is None:
- relationships = [ConceptRelation(self, None)]
+ def deassignChildren(self, concept, predicates=None):
registry = zapi.getUtility(IRelationRegistry)
- relations = []
- for rs in relationships:
- relations.extend(registry.query(first=self, second=concept,
- relationship=rs))
- for rel in relations:
+ #relations = []
+ #for rs in relationships:
+ # relations.extend(registry.query(first=self, second=concept,
+ # relationship=rs))
+ for rel in self.getChildRelations(predicates, concept):
registry.unregister(rel)
- def deassignParents(self, concept, relationships=None):
- concept.deassignChildren(self, relationships)
+ def deassignParents(self, concept, predicates=None):
+ concept.deassignChildren(self, predicates)
# resource relations
- def getResources(self, relationships=None):
- if relationships is None:
- relationships = [ResourceRelation]
- rels = getRelations(first=self, relationships=relationships)
- return [r.second for r in rels]
+ def getResourceRelations(self, predicates=None):
+ predicates = predicates is None and ['*'] or predicates
+ relationships = [ResourceRelation(self, None, p) for p in predicates]
# TODO: sort...
+ return getRelations(first=self, relationships=relationships)
- def assignResource(self, resource, relationship=ResourceRelation):
+ def getResources(self, predicates=None):
+ return [r.second for r in self.getResourceRelations(predicates)]
+
+ def assignResource(self, resource, predicate=None):
+ if predicate is None:
+ predicate = self.getConceptManager().getDefaultPredicate()
registry = zapi.getUtility(IRelationRegistry)
- registry.register(relationship(self, resource))
+ registry.register(ResourceRelation(self, resource, predicate))
# TODO (?): avoid duplicates
- def deassignResource(self, resource, relationships=None):
- if relationships is None:
- relationships = [ResourceRelation]
+ def deassignResource(self, resource, predicates=None):
registry = zapi.getUtility(IRelationRegistry)
- relations = registry.query(first=self, second=resource,
- relationships=relationships)
- for rel in relations:
- registry.unregister(relation)
+ for rel in self.getResourceRelations(predicates):
+ registry.unregister(rel)
# concept manager
diff --git a/interfaces.py b/interfaces.py
index 9688c8e..981f5f1 100644
--- a/interfaces.py
+++ b/interfaces.py
@@ -79,56 +79,69 @@ class IConcept(ILoopsObject, IPotentialTarget):
source="loops.conceptTypeSource",
required=False)
- def getChildren(relationships=None):
+ def getChildren(predicates=None):
""" Return a sequence of concepts related to self as child concepts,
- possibly restricted to the relationships (typically a list of
- relation classes) given.
+ optionally restricted to the predicates given.
"""
- def getParents(relationships=None):
+ def getChildRelations(predicates=None):
+ """ Return a sequence of relations to other concepts assigned to self
+ as child concepts, optionally restricted to the predicates given.
+ """
+
+ def getParents(predicates=None):
""" Return a tuple of concepts related to self as parent concepts,
- possibly restricted to the relationships (typically a list of
- relation classes) given.
+ optionally restricted to the predicates given.
"""
- def assignChild(concept, relationship):
- """ Assign an existing concept to self using the relationship given.
+ def getParentRelations(predicates=None):
+ """ Return a sequence of relations to other concepts assigned to self
+ as child concepts, optionally restricted to the predicates given.
+ """
+
+ def assignChild(concept, predicate):
+ """ Assign an existing concept to self using the predicate given.
The assigned concept will be a child concept of self.
- The relationship defaults to ConceptRelation.
+ The predicate defaults to the concept manager's default predicate.
"""
- def assignParent(concept, relationship):
- """ Assign an existing concept to self using the relationship given.
+ def assignParent(concept, predicate):
+ """ Assign an existing concept to self using the predicate given.
The assigned concept will be a parent concept of self.
- The relationship defaults to ConceptRelation.
+ The predicate defaults to the concept manager's default predicate.
"""
- def deassignChildren(concept, relationships=None):
+ def deassignChildren(concept, predicates=None):
""" Remove the child concept relations to the concept given from self,
- optionally restricting them to the relationships given.
+ optionally restricting them to the predicates given.
"""
- def deassignParents(concept, relationships=None):
+ def deassignParents(concept, predicates=None):
""" Remove the child concept relations to the concept given from self,
- optionally restricting them to the relationships given.
+ optionally restricting them to the predicates given.
"""
- def getResources(relationships=None):
+ def getResources(predicates=None):
""" Return a sequence of resources assigned to self,
- possibly restricted to the relationships given.
+ optionally restricted to the predicates given.
"""
- def assignResource(resource, relationship):
- """ Assign an existing resource to self using the relationship given.
+ def getResourceRelations(predicates=None):
+ """ Return a sequence of relations to resources assigned to self,
+ optionally restricted to the predicates given.
+ """
+
+ def assignResource(resource, predicate):
+ """ Assign an existing resource to self using the predicate given.
The relationship defaults to ConceptResourceRelation.
"""
- def deassignResource(resource, relationships=None):
+ def deassignResource(resource, predicates=None):
""" Remove the relations to the resource given from self, optionally
- restricting them to the relationships given.
+ restricting them to the predicates given.
"""