Work in progress: concept relations using predicates...

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1092 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2006-02-22 09:44:29 +00:00
parent 8477eb47b9
commit 07b62baa25
6 changed files with 222 additions and 105 deletions

View file

@ -49,7 +49,7 @@ default predicate concept; the default name for this is 'standard'.
>>> from cybertools.relation.registry import DummyRelationRegistry >>> from cybertools.relation.registry import DummyRelationRegistry
>>> from zope.app.testing import ztapi >>> from zope.app.testing import ztapi
>>> ztapi.provideUtility(IRelationRegistry, DummyRelationRegistry()) >>> 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 Now we can assign the concept c2 as a child to c1 (using the standard
ConceptRelation): ConceptRelation):
@ -58,20 +58,13 @@ ConceptRelation):
We can now ask our concepts for their related child and parent concepts: We can now ask our concepts for their related child and parent concepts:
>>> sc1 = cc1.getChildren() >>> [zapi.getName(c) for c in cc1.getChildren()]
>>> len(sc1) [u'cc2']
1
>>> cc2 in sc1
True
>>> len(cc1.getParents()) >>> len(cc1.getParents())
0 0
>>> [zapi.getName(p) for p in cc2.getParents()]
[u'cc1']
>>> pc2 = cc2.getParents()
>>> len(pc2)
1
>>> cc1 in pc2
True
>>> len(cc2.getChildren()) >>> len(cc2.getChildren())
0 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 This type object is its own type. The type relations themselves are of
a special predicate 'hasType'. a special predicate 'hasType'.
>>> concepts['hasType'] = Concept(u'Type Predicate') >>> concepts['hasType'] = Concept(u'has type')
>>> concepts['type'] = Concept(u'Type') >>> concepts['type'] = Concept(u'Type')
>>> typeObject = concepts['type'] >>> typeObject = concepts['type']
>>> typeObject.setConceptType(typeObject) >>> typeObject.setConceptType(typeObject)
@ -117,9 +110,18 @@ Concept Views
>>> from loops.browser.concept import ConceptView >>> from loops.browser.concept import ConceptView
>>> view = ConceptView(cc1, TestRequest()) >>> 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'] [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: The concept view allows updating the underlying context object:
>>> cc3 = Concept(u'loops for Zope 3') >>> cc3 = Concept(u'loops for Zope 3')
@ -133,7 +135,7 @@ The concept view allows updating the underlying context object:
>>> view = ConceptView(cc1, >>> view = ConceptView(cc1,
... TestRequest(action='remove', qualifier='children', ... TestRequest(action='remove', qualifier='children',
... tokens=['.loops/concepts/cc2'])) ... tokens=['.loops/concepts/cc2:.loops/concepts/standard']))
>>> view.update() >>> view.update()
True True
>>> sorted(c.title for c in cc1.getChildren()) >>> sorted(c.title for c in cc1.getChildren())

View file

@ -32,8 +32,8 @@ from zope.security.proxy import removeSecurityProxy
class BaseView(object): class BaseView(object):
def __init__(self, context, request): def __init__(self, context, request):
self.context = context #self.context = context
#self.context = removeSecurityProxy(context) self.context = removeSecurityProxy(context)
self.request = request self.request = request
@Lazy @Lazy
@ -64,6 +64,15 @@ class BaseView(object):
def value(self): def value(self):
return self.context 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): class LoopsTerms(object):
""" Provide the ITerms interface, e.g. for usage in selection """ Provide the ITerms interface, e.g. for usage in selection

View file

@ -40,12 +40,22 @@ from loops.browser.common import BaseView, LoopsTerms
class ConceptView(BaseView): class ConceptView(BaseView):
def children(self): def children(self):
ch = self.context.getChildren() for r in self.context.getChildRelations():
return ch and self.viewIterator(ch) or [] yield ConceptRelationView(r, self.request, contextIsSecond=True)
def parents(self): def parents(self):
p = self.context.getParents() for r in self.context.getParentRelations():
return p and self.viewIterator(p) or [] 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): def viewIterator(self, objs):
request = self.request request = self.request
@ -61,6 +71,10 @@ class ConceptView(BaseView):
return True return True
tokens = self.request.get('tokens', []) tokens = self.request.get('tokens', [])
for token in tokens: for token in tokens:
parts = token.split(':')
token = parts[0]
if len(parts) > 1:
relToken = parts[1]
concept = self.loopsRoot.loopsTraverse(token) concept = self.loopsRoot.loopsTraverse(token)
if action == 'assign': if action == 'assign':
assignAs = self.request.get('assignAs', 'child') assignAs = self.request.get('assignAs', 'child')
@ -71,11 +85,12 @@ class ConceptView(BaseView):
else: else:
raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs) raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs)
elif action == 'remove': elif action == 'remove':
predicate = self.loopsRoot.loopsTraverse(relToken)
qualifier = self.request.get('qualifier') qualifier = self.request.get('qualifier')
if qualifier == 'parents': if qualifier == 'parents':
self.context.deassignParents(concept) self.context.deassignParents(concept, [predicate])
elif qualifier == 'children': elif qualifier == 'children':
self.context.deassignChildren(concept) self.context.deassignChildren(concept, [predicate])
else: else:
raise(BadRequest, 'Illegal qualifier: %s.' % qualifier) raise(BadRequest, 'Illegal qualifier: %s.' % qualifier)
else: else:
@ -102,12 +117,6 @@ class ConceptView(BaseView):
else: else:
raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs) 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): def search(self):
request = self.request request = self.request
if request.get('action') != 'search': if request.get('action') != 'search':
@ -120,3 +129,50 @@ class ConceptView(BaseView):
result = self.loopsRoot.getConceptManager().values() result = self.loopsRoot.getConceptManager().values()
return self.viewIterator(result) 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)

View file

@ -22,6 +22,8 @@
<tr> <tr>
<th>&nbsp;</th> <th>&nbsp;</th>
<th i18n:translate="label_title">Title</th> <th i18n:translate="label_title">Title</th>
<th i18n:translate="label_predicate">Predicate</th>
<th i18n:translate="label_type">Type</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -37,6 +39,23 @@
Title Title
</a> </a>
</td> </td>
<tal:relation condition="item/predicateTitle|nothing">
<td>
<a tal:content="item/predicateTitle" href="#"
tal:attributes="href
string:${item/predicateUrl}/@@SelectedManagementView.html">
Type
</a>
</td>
</tal:relation>
<td>
<a tal:condition="item/typeTitle | nothing"
tal:content="item/typeTitle" href="#"
tal:attributes="href
string:${item/typeUrl}/@@SelectedManagementView.html">
Title
</a>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -45,13 +45,10 @@ from interfaces import ISearchableText
# relation classes # relation classes
class ConceptRelation(DyadicRelation): class BaseRelation(DyadicRelation):
""" A relation between concept objects.
"""
implements(IConceptRelation)
def __init__(self, first, second, predicate=None): def __init__(self, first, second, predicate=None):
super(ConceptRelation, self).__init__(first, second) super(BaseRelation, self).__init__(first, second)
if predicate is None: if predicate is None:
context = first is not None and first or second context = first is not None and first or second
cm = context.getLoopsRoot().getConceptManager() cm = context.getLoopsRoot().getConceptManager()
@ -59,12 +56,18 @@ class ConceptRelation(DyadicRelation):
self.predicate = predicate self.predicate = predicate
def getPredicateName(self): def getPredicateName(self):
baseName = super(ConceptRelation, self).getPredicateName() baseName = super(BaseRelation, self).getPredicateName()
id = zapi.getUtility(IRelationRegistry).getUniqueIdForObject(self.predicate) id = zapi.getUtility(IRelationRegistry).getUniqueIdForObject(self.predicate)
return '.'.join((baseName, str(id))) 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. """ A relation between a concept and a resource object.
""" """
implements(IConceptRelation) implements(IConceptRelation)
@ -84,16 +87,23 @@ class Concept(Contained, Persistent):
title = property(getTitle, setTitle) title = property(getTitle, setTitle)
def getConceptType(self): def getConceptType(self):
cm = self.getLoopsRoot().getConceptManager() typePred = self.getConceptManager().getTypePredicate()
typeRelation = ConceptRelation(None, self, cm.getTypePredicate()) parents = self.getParents([typePred])
rel = getRelationSingle(self, typeRelation, forSecond=True) #typeRelation = ConceptRelation(None, self, cm.getTypePredicate())
return rel and rel.first or None #rels = getRelationSingle(self, typeRelation, forSecond=True)
# TODO (?): check for multiple types (->Error)
return parents and parents[0] or None
def setConceptType(self, concept): def setConceptType(self, concept):
if self.getConceptType() != concept: current = self.getConceptType()
cm = self.getLoopsRoot().getConceptManager() if current != concept:
typeRelation = ConceptRelation(removeSecurityProxy(concept), self, typePred = self.getConceptManager().getTypePredicate()
cm.getTypePredicate()) if current is not None:
setRelationSingle(typeRelation, forSecond=True) 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) conceptType = property(getConceptType, setConceptType)
def __init__(self, title=u''): def __init__(self, title=u''):
@ -102,66 +112,74 @@ class Concept(Contained, Persistent):
def getLoopsRoot(self): def getLoopsRoot(self):
return zapi.getParent(self).getLoopsRoot() return zapi.getParent(self).getLoopsRoot()
def getConceptManager(self):
return self.getLoopsRoot().getConceptManager()
# concept relations # concept relations
def getChildren(self, relationships=None): def getChildRelations(self, predicates=None, second=None):
if relationships is None: predicates = predicates is None and ['*'] or predicates
relationships = [ConceptRelation(self, None)] relationships = [ConceptRelation(self, None, p) for p in predicates]
rels = getRelations(first=self, relationships=relationships)
return [r.second for r in rels]
# TODO: sort... # TODO: sort...
return getRelations(first=self, second=second, relationships=relationships)
def getParents(self, relationships=None): def getChildren(self, predicates=None):
if relationships is None: return [r.second for r in self.getChildRelations(predicates)]
relationships = [ConceptRelation(None, self)]
rels = getRelations(second=self, relationships=relationships)
return [r.first for r in rels]
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) registry = zapi.getUtility(IRelationRegistry)
rel = relationship(self, concept) rel = ConceptRelation(self, concept, predicate)
registry.register(rel) registry.register(rel)
# TODO (?): avoid duplicates # TODO (?): avoid duplicates
def assignParent(self, concept, relationship=ConceptRelation): def assignParent(self, concept, predicate=None):
concept.assignChild(self, relationship) concept.assignChild(self, predicate)
def deassignChildren(self, concept, relationships=None): def deassignChildren(self, concept, predicates=None):
if relationships is None:
relationships = [ConceptRelation(self, None)]
registry = zapi.getUtility(IRelationRegistry) registry = zapi.getUtility(IRelationRegistry)
relations = [] #relations = []
for rs in relationships: #for rs in relationships:
relations.extend(registry.query(first=self, second=concept, # relations.extend(registry.query(first=self, second=concept,
relationship=rs)) # relationship=rs))
for rel in relations: for rel in self.getChildRelations(predicates, concept):
registry.unregister(rel) registry.unregister(rel)
def deassignParents(self, concept, relationships=None): def deassignParents(self, concept, predicates=None):
concept.deassignChildren(self, relationships) concept.deassignChildren(self, predicates)
# resource relations # resource relations
def getResources(self, relationships=None): def getResourceRelations(self, predicates=None):
if relationships is None: predicates = predicates is None and ['*'] or predicates
relationships = [ResourceRelation] relationships = [ResourceRelation(self, None, p) for p in predicates]
rels = getRelations(first=self, relationships=relationships)
return [r.second for r in rels]
# TODO: sort... # 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 = zapi.getUtility(IRelationRegistry)
registry.register(relationship(self, resource)) registry.register(ResourceRelation(self, resource, predicate))
# TODO (?): avoid duplicates # TODO (?): avoid duplicates
def deassignResource(self, resource, relationships=None): def deassignResource(self, resource, predicates=None):
if relationships is None:
relationships = [ResourceRelation]
registry = zapi.getUtility(IRelationRegistry) registry = zapi.getUtility(IRelationRegistry)
relations = registry.query(first=self, second=resource, for rel in self.getResourceRelations(predicates):
relationships=relationships) registry.unregister(rel)
for rel in relations:
registry.unregister(relation)
# concept manager # concept manager

View file

@ -79,56 +79,69 @@ class IConcept(ILoopsObject, IPotentialTarget):
source="loops.conceptTypeSource", source="loops.conceptTypeSource",
required=False) required=False)
def getChildren(relationships=None): def getChildren(predicates=None):
""" Return a sequence of concepts related to self as child concepts, """ Return a sequence of concepts related to self as child concepts,
possibly restricted to the relationships (typically a list of optionally restricted to the predicates given.
relation classes) 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, """ Return a tuple of concepts related to self as parent concepts,
possibly restricted to the relationships (typically a list of optionally restricted to the predicates given.
relation classes) given.
""" """
def assignChild(concept, relationship): def getParentRelations(predicates=None):
""" Assign an existing concept to self using the relationship given. """ 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 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): def assignParent(concept, predicate):
""" Assign an existing concept to self using the relationship given. """ Assign an existing concept to self using the predicate given.
The assigned concept will be a parent concept of self. 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, """ 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, """ 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, """ Return a sequence of resources assigned to self,
possibly restricted to the relationships given. optionally restricted to the predicates given.
""" """
def assignResource(resource, relationship): def getResourceRelations(predicates=None):
""" Assign an existing resource to self using the relationship given. """ 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. 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 """ Remove the relations to the resource given from self, optionally
restricting them to the relationships given. restricting them to the predicates given.
""" """