loops/loops/concept.py

530 lines
19 KiB
Python

# loops.concept
""" Definition of the Concept and related classes.
"""
from zope import component, schema
from zope.authentication.interfaces import IAuthentication, PrincipalLookupError
from zope.browser.interfaces import IAdding
from zope.cachedescriptors.property import Lazy
from zope.component import adapts
from zope.container.btree import BTreeContainer
from zope.container.contained import Contained
from zope.dublincore.interfaces import IZopeDublinCore
from zope.event import notify
from zope.interface import implementer
from zope.interface import alsoProvides, directlyProvides, directlyProvidedBy
from zope.interface.interfaces import ObjectEvent
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.security.proxy import removeSecurityProxy, isinstance
from zope.traversing.api import getName, getParent
from persistent import Persistent
from cybertools.meta.interfaces import IOptions
from cybertools.relation import DyadicRelation
from cybertools.relation.registry import getRelations
from cybertools.relation.interfaces import IRelationRegistry, IRelatable
from cybertools.typology.interfaces import IType, ITypeManager
from cybertools.util.jeep import Jeep
from loops.base import ParentInfo
from loops.common import adapted, baseObject, AdapterBase
from loops.i18n.common import I18NValue
from loops.interfaces import IConcept, IConceptRelation, IConceptView
from loops.interfaces import IResource
from loops.interfaces import IConceptManager, IConceptManagerContained
from loops.interfaces import ILoopsContained
from loops.interfaces import IIndexAttributes
from loops.interfaces import IIsSubtype
from loops.interfaces import IAssignmentEvent, IDeassignmentEvent
from loops.security.common import canListObject
from loops import util
from loops.versioning.util import getMaster
from loops.view import TargetRelation
# relation classes
class BaseRelation(DyadicRelation):
fallback = '*'
def __init__(self, first, second, predicate=None):
super(BaseRelation, 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
def getPredicateName(self):
if self.predicate is None:
return None
baseName = super(BaseRelation, self).getPredicateName()
id = util.getUidForObject(self.predicate)
return '.'.join((baseName, id))
@property
def ident(self):
return self.predicate
# Problem with reindex catalog, needs __parent__ - but this does not help:
#__parent__ = None
#@property
#def __parent__(self):
# return self.first
# So we patched zope.location.location, line 109...
@implementer(IConceptRelation)
class ConceptRelation(BaseRelation):
""" A relation between concept objects.
"""
fallback = 'c*'
@implementer(IConceptRelation)
class ResourceRelation(BaseRelation):
""" A relation between a concept and a resource object.
"""
fallback = 'r*'
# concept
@implementer(IConcept, IConceptManagerContained, IRelatable)
class Concept(Contained, Persistent):
proxyInterface = IConceptView
workspaceInformation = None
metaInfo = u''
def __init__(self, title=u''):
self.title = title
_title = u''
def getTitle(self): return self._title
def setTitle(self, title): self._title = title
title = property(getTitle, setTitle)
_description = u''
def getDescription(self): return self._description
def setDescription(self, description): self._description = description
description = property(getDescription, setDescription)
def getConceptType(self):
typePred = self.getConceptManager().getTypePredicate()
if typePred is None:
return None
parents = self.getParents([typePred], noSecurityCheck=True)
# TODO (?): check for multiple types (->Error)
return parents and parents[0] or None
def setConceptType(self, concept):
current = self.getConceptType()
if current != concept:
typePred = self.getConceptManager().getTypePredicate()
if typePred is None:
raise ValueError('No type predicate found for '
+ getName(self))
if current is not None:
self.deassignParent(current, [typePred])
self.assignParent(concept, typePred)
conceptType = property(getConceptType, setConceptType)
def getType(self):
return self.conceptType
def setType(self, value):
self.conceptType = value
def getLoopsRoot(self):
return getParent(self).getLoopsRoot()
def getConceptManager(self):
return self.getLoopsRoot().getConceptManager()
def getAllParents(self, collectGrants=False, result=None, ignoreTypes=False):
if result is None:
result = Jeep()
for rel in self.getParentRelations():
if (ignoreTypes and
rel.predicate == self.getConceptManager().getTypePredicate()):
continue
obj = rel.first
uid = util.getUidForObject(obj)
pi = result.get(uid)
if pi is None:
result[uid] = ParentInfo(obj, [rel])
obj.getAllParents(collectGrants, result, ignoreTypes)
elif rel not in pi.relations:
pi.relations.append(rel)
return result
def getLongTitle(self):
return self.title
@property
def favTitle(self):
return self.title
# concept relations
def getClients(self, relationships=None):
if relationships is None:
relationships = [TargetRelation]
rels = getRelations(second=self, relationships=relationships)
return [r.first for r in rels if canListObject(r.first)]
def getChildRelations(self, predicates=None, child=None, sort='default',
noSecurityCheck=False, usePredicateIndex=False):
predicates = predicates is None and ['c*'] or predicates
relationships = [ConceptRelation(self, None, p) for p in predicates]
if sort == 'default':
sort = lambda x: (x.order, (x.second.title and x.second.title.lower()))
rels = (r for r in getRelations(self, child, relationships=relationships,
usePredicateIndex=usePredicateIndex)
if canListObject(r.second, noSecurityCheck) and
IConcept.providedBy(r.second))
return sorted(rels, key=sort)
def getChildren(self, predicates=None, sort='default',
noSecurityCheck=False, usePredicateIndex=False):
return [r.second for r in self.getChildRelations(
predicates, sort=sort,
noSecurityCheck=noSecurityCheck,
usePredicateIndex=usePredicateIndex)]
def getParentRelations (self, predicates=None, parent=None, sort='default',
noSecurityCheck=False, usePredicateIndex=False):
predicates = predicates is None and ['c*'] or predicates
relationships = [ConceptRelation(None, self, p) for p in predicates]
if sort == 'default':
sort = lambda x: (x.first.title and x.first.title.lower())
rels = (r for r in getRelations(parent, self, relationships=relationships,
usePredicateIndex=usePredicateIndex)
if canListObject(r.first, noSecurityCheck))
return sorted(rels, key=sort)
def getParents(self, predicates=None, sort='default',
noSecurityCheck=False, usePredicateIndex=False):
return [r.first for r in self.getParentRelations(
predicates, sort=sort,
noSecurityCheck=noSecurityCheck,
usePredicateIndex=usePredicateIndex)]
def checkPredicate(self, child, predicate=None):
cm = self.getConceptManager()
defaultPredicate = cm.getDefaultPredicate()
if predicate is None:
predicate = defaultPredicate
if predicate == defaultPredicate:
subtypePred = cm.get('issubtype')
if subtypePred is not None:
subtypeRels = list(self.conceptType.getChildRelations(
[subtypePred], child.conceptType))
if subtypeRels:
from loops.predicate import adaptedRelation
rel = adaptedRelation(subtypeRels[0])
if IIsSubtype.providedBy(rel):
predName = rel.usePredicate
if predName and predName != u'standard':
predicate = cm[predName]
return predicate
def assignChild(self, concept, predicate=None, order=0, relevance=1.0):
self.createChildRelation(concept, predicate, order, relevance)
def createChildRelation(self, concept, predicate=None, order=0, relevance=1.0):
predicate = self.checkPredicate(concept, predicate)
registry = component.getUtility(IRelationRegistry)
rel = ConceptRelation(self, concept, predicate)
if order != 0:
rel.order = order
if relevance != 1.0:
rel.relevance = relevance
# TODO (?): avoid duplicates
registry.register(rel)
notify(AssignmentEvent(self, rel))
return rel
def setChildren(self, predicate, concepts):
existing = self.getChildren([predicate])
for c in existing:
if c not in concepts:
self.deassignChild(c, [predicate])
for c in concepts:
if c not in existing:
self.assignChild(c, predicate)
def assignParent(self, concept, predicate=None, order=0, relevance=1.0):
concept.assignChild(self, predicate, order, relevance)
def setParents(self, predicate, concepts):
existing = self.getParents([predicate])
for c in existing:
if c not in concepts:
self.deassignParent(c, [predicate])
for c in concepts:
if c not in existing:
self.assignParent(c, predicate)
def deassignChild(self, child, predicates=None, order=None, noSecurityCheck=False):
registry = component.getUtility(IRelationRegistry)
for rel in self.getChildRelations(predicates, child,
noSecurityCheck=noSecurityCheck):
if order is None or rel.order == order:
registry.unregister(rel)
notify(DeassignmentEvent(self, rel))
def deassignParent(self, parent, predicates=None, noSecurityCheck=False):
parent.deassignChild(self, predicates)
# resource relations
def getResourceRelations(self, predicates=None, resource=None, sort='default',
noSecurityCheck=False, usePredicateIndex=False):
if resource is not None:
resource = getMaster(resource)
predicates = predicates is None and ['r*'] or predicates
relationships = [ResourceRelation(self, None, p) for p in predicates]
if sort == 'default':
sort = lambda x: (x.order, x.second.title.lower())
rels = (r for r in getRelations(self, resource, relationships=relationships,
usePredicateIndex=usePredicateIndex)
if canListObject(r.second, noSecurityCheck) and
IResource.providedBy(r.second))
return sorted(rels, key=sort)
def getResources(self, predicates=None, sort='default',
noSecurityCheck=False, usePredicateIndex=False):
return [r.second for r in self.getResourceRelations(
predicates, sort=sort,
noSecurityCheck=noSecurityCheck,
usePredicateIndex=usePredicateIndex)]
def assignResource(self, resource, predicate=None, order=0, relevance=1.0):
resource = getMaster(resource)
if predicate is None:
predicate = self.getConceptManager().getDefaultPredicate()
registry = component.getUtility(IRelationRegistry)
rel = ResourceRelation(self, resource, predicate)
if order != 0:
rel.order = order
if relevance != 1.0:
rel.relevance = relevance
# TODO (?): avoid duplicates
registry.register(rel)
notify(AssignmentEvent(self, rel))
def deassignResource(self, resource, predicates=None, order=None):
resource = getMaster(resource)
registry = component.getUtility(IRelationRegistry)
for rel in self.getResourceRelations(predicates, resource):
if order is None or rel.order == order:
registry.unregister(rel)
notify(DeassignmentEvent(self, rel))
# combined children+resources query
def getChildAndResourceRelations(self, predicates=None, sort='default'):
if predicates is None:
predicates = [self.getConceptManager().getDefaultPredicate()]
relationships = ([ResourceRelation(self, None, p) for p in predicates]
+ [ConceptRelation(None, self, p) for p in predicates])
if sort == 'default':
sort = lambda x: (x.order, x.second.title.lower())
rels = (r for r in getRelations(self, child, relationships=relationships)
if canListObject(r.second))
return sorted(rels, key=sort)
# concept manager
@implementer(IConceptManager, ILoopsContained)
class ConceptManager(BTreeContainer):
typeConcept = None
typePredicate = None
defaultPredicate = None
predicateType = None
def getLoopsRoot(self):
return getParent(self)
def getAllParents(self, collectGrants=False):
return Jeep()
def getTypePredicate(self):
return self.get('hasType')
def getTypeConcept(self):
if self.typeConcept is None:
self.typeConcept = self.get('type')
return self.typeConcept
def getDefaultPredicate(self):
if self.defaultPredicate is None:
self.defaultPredicate = self.get('standard')
return self.defaultPredicate
def getPredicateType(self):
if self.predicateType is None:
dp = self.getDefaultPredicate()
self.predicateType = dp.conceptType
return self.predicateType
def getViewManager(self):
return self.getLoopsRoot().getViewManager()
# adapters and similar components
@implementer(schema.interfaces.IIterableSource)
class ConceptTypeSourceList(object):
def __init__(self, context):
if IBrowserRequest.providedBy(context):
context = context.context
if IAdding.providedBy(context):
context = context.context
if isinstance(context, AdapterBase):
context = context.context
self.context = context
def __iter__(self):
return iter(self.conceptTypes)
@Lazy
def conceptTypes(self):
types = ITypeManager(self.context).listTypes(include=('concept',))
return [t.typeProvider for t in types]
def __len__(self):
return len(self.conceptTypes)
@implementer(schema.interfaces.IIterableSource)
class PredicateSourceList(object):
def __init__(self, context):
self.context = context
self.concepts = self.context.getLoopsRoot().getConceptManager()
def __iter__(self):
return iter(self.predicates)
@Lazy
def predicates(self):
result = []
cm = self.concepts
defPred = cm.getDefaultPredicate()
typePred = cm.getTypePredicate()
if defPred is not None and typePred is not None:
result.append(defPred)
predType = defPred.conceptType
if predType is not None and predType != cm.getTypeConcept():
result.extend(p for p in predType.getChildren([typePred])
if p not in result
and p != typePred)
return result
def __len__(self):
return len(self.predicates)
@implementer(IIndexAttributes)
class IndexAttributes(object):
adapts(IConcept)
def __init__(self, context):
self.context = context
@Lazy
def adapted(self):
return adapted(self.context)
@Lazy
def adaptedIndexAttributes(self):
#if self.adapted != self.context:
if isinstance(self.adapted, AdapterBase):
#return component.queryAdapter(self.adapted, IIndexAttributes)
iattr = IIndexAttributes(self.adapted, None)
if iattr.__class__ == self.__class__:
return None
return iattr
def text(self):
if self.adaptedIndexAttributes is not None:
return self.adaptedIndexAttributes.text()
description = self.context.description
if description is None:
description = u''
if isinstance(description, I18NValue):
description = ' '.join(description.values())
actx = self.adapted
indexAttrs = getattr(actx, '_textIndexAttributes', ())
indexValues = [getattr(actx, attr, u'???') for attr in indexAttrs]
return ' '.join([self.title(), description] +
[c for c in self.creators() if c is not None] +
[v for v in indexValues if v is not None]).strip()
def title(self):
if self.adaptedIndexAttributes is not None:
return self.adaptedIndexAttributes.title()
context = self.context
title = context.title
if title is None:
title = u''
if isinstance(title, I18NValue):
title = ' '.join(title.values())
return ' '.join((getName(baseObject(context)), title)).strip()
def date(self):
if self.adaptedIndexAttributes is not None:
return self.adaptedIndexAttributes.date()
def creators(self):
cr = IZopeDublinCore(baseObject(self.context)).creators or []
pau = component.getUtility(IAuthentication)
creators = []
for c in cr:
try:
principal = pau.getPrincipal(c)
if principal is None:
creators.append(c)
else:
creators.append(principal.title)
except PrincipalLookupError:
creators.append(c)
return creators
def identifier(self):
id = getattr(self.adapted, 'identifier', None)
if id is None:
return getName(baseObject(self.context))
return id
def keywords(self):
if self.adaptedIndexAttributes is not None:
return self.adaptedIndexAttributes.keywords()
# events
@implementer(IAssignmentEvent)
class AssignmentEvent(ObjectEvent):
def __init__(self, obj, relation):
super(AssignmentEvent, self).__init__(obj)
self.relation = relation
@implementer(IDeassignmentEvent)
class DeassignmentEvent(ObjectEvent):
def __init__(self, obj, relation):
super(DeassignmentEvent, self).__init__(obj)
self.relation = relation