diff --git a/README.txt b/README.txt
index cc599ac..42f471c 100755
--- a/README.txt
+++ b/README.txt
@@ -768,6 +768,25 @@ target object's view here:
'http://127.0.0.1/loops/views/m1/m11/m111/.target23'
+Collecting Information about Parents
+====================================
+
+Sometimes, e.g. when checking permissions, it is important to collect
+informations about all parents of an object.
+
+ >>> parents = m113.getAllParents()
+ >>> for p in parents:
+ ... print p.object.title
+ Zope
+ Menu
+
+ >>> parents = resources['test_note'].getAllParents()
+ >>> for p in parents:
+ ... print p.object.title, len(p.relations)
+ Note 1
+ Type 2
+
+
Import/Export
=============
diff --git a/base.py b/base.py
index febeb27..35aa69d 100644
--- a/base.py
+++ b/base.py
@@ -29,6 +29,7 @@ from zope.app.folder.interfaces import IFolder
from zope.traversing.api import getPath, traverse
from zope.interface import implements
+from cybertools.util.jeep import Jeep
from loops.interfaces import ILoops
loopsPrefix = '.loops'
@@ -58,6 +59,9 @@ class Loops(Folder):
def getLoopsRoot(self):
return self
+ def getAllParents(self, collectGrants=False):
+ return Jeep()
+
def getConceptManager(self):
return self['concepts']
@@ -76,6 +80,14 @@ class Loops(Folder):
return traverse(self, uri[len(prefix):])
+class ParentInfo(object):
+
+ def __init__(self, obj, relations=None, grants=None):
+ self.object = obj
+ self.relations = relations or []
+ self.grants = grants or []
+
+
# backward compatibility for old loops sites that got their Loops object
# directly from loops/__init__.py
import loops
diff --git a/classifier/base.py b/classifier/base.py
index f02db4e..27b4348 100644
--- a/classifier/base.py
+++ b/classifier/base.py
@@ -61,7 +61,7 @@ class Classifier(AdapterBase):
for name in self.extractors.split():
extractor = component.getAdapter(adapted(resource), IExtractor, name=name)
infoSet.update(extractor.extractInformationSet())
- analyzer = component.getAdapter(self, name=self.analyzer)
+ analyzer = component.getAdapter(self, IAnalyzer, name=self.analyzer)
statements = analyzer.extractStatements(infoSet)
defaultPredicate = self.context.getConceptManager().getDefaultPredicate()
for statement in statements:
diff --git a/classifier/browser.py b/classifier/browser.py
index 23d3cae..6d32e9f 100644
--- a/classifier/browser.py
+++ b/classifier/browser.py
@@ -28,3 +28,36 @@ from zope.cachedescriptors.property import Lazy
from loops.browser.concept import ConceptView
from loops.common import adapted
+
+
+class ClassifierView(ConceptView):
+
+ macro_template = ViewPageTemplateFile('classifier_macros.pt')
+
+ @property
+ def macro(self):
+ return self.macro_template.macros['render_classifier']
+
+ def update(self):
+ if 'update' in self.request.form:
+ cta = adapted(self.context)
+ if cta is not None:
+ for r in collectResources(self.context):
+ print '***', r.title
+ cta.process(r)
+ return True
+
+
+def collectResources(concept, checkedConcepts=None, result=None):
+ if result is None:
+ result = []
+ if checkedConcepts is None:
+ checkedConcepts = []
+ for r in concept.getResources():
+ if r not in result:
+ result.append(r)
+ for c in concept.getChildren():
+ if c not in checkedConcepts:
+ checkedConcepts.append(c)
+ collectResources(c, checkedConcepts, result)
+ return result
diff --git a/classifier/classifier_macros.pt b/classifier/classifier_macros.pt
new file mode 100644
index 0000000..785957f
--- /dev/null
+++ b/classifier/classifier_macros.pt
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/classifier/configure.zcml b/classifier/configure.zcml
index 49e6742..bf5c528 100644
--- a/classifier/configure.zcml
+++ b/classifier/configure.zcml
@@ -30,15 +30,25 @@
-
+
+
+
+
+
diff --git a/classifier/testsetup.py b/classifier/testsetup.py
index a932a82..a529bce 100644
--- a/classifier/testsetup.py
+++ b/classifier/testsetup.py
@@ -43,8 +43,6 @@ class TestSite(BaseTestSite):
component.provideAdapter(OrganizeSetupManager, name='organize')
concepts, resources, views = self.baseSetup()
- #catalog = component.getUtility(ICatalog)
- #catalog['loops_text'] = TextIndex('text', IIndexAttributes, True)
# classifier and Co
tType = concepts.getTypeConcept()
tClassifier = addAndConfigureObject(concepts, Concept, 'classifier',
diff --git a/concept.py b/concept.py
index 0d41b79..7d89983 100644
--- a/concept.py
+++ b/concept.py
@@ -39,11 +39,14 @@ from cybertools.relation.registry import getRelations
from cybertools.relation.registry import getRelationSingle, setRelationSingle
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.interfaces import IConcept, IConceptRelation, IConceptView
from loops.interfaces import IConceptManager, IConceptManagerContained
from loops.interfaces import ILoopsContained
from loops.interfaces import IIndexAttributes
+from loops import util
from loops.view import TargetRelation
@@ -132,6 +135,20 @@ class Concept(Contained, Persistent):
def getConceptManager(self):
return self.getLoopsRoot().getConceptManager()
+ def getAllParents(self, collectGrants=False, result=None):
+ if result is None:
+ result = Jeep()
+ for rel in self.getParentRelations():
+ obj = rel.first
+ uid = util.getUidForObject(obj)
+ pi = result.get(uid)
+ if pi is None:
+ result[uid] = ParentInfo(obj, [rel])
+ obj.getAllParents(collectGrants, result)
+ elif rel not in pi.relations:
+ pi.relations.append(rel)
+ return result
+
# concept relations
def getClients(self, relationships=None):
@@ -215,6 +232,9 @@ class ConceptManager(BTreeContainer):
def getLoopsRoot(self):
return zapi.getParent(self)
+ def getAllParents(self, collectGrants=False):
+ return Jeep()
+
def getTypePredicate(self):
return self.get('hasType')
diff --git a/integrator/browser.py b/integrator/browser.py
index 626d694..1bbba07 100644
--- a/integrator/browser.py
+++ b/integrator/browser.py
@@ -28,6 +28,7 @@ from zope.cachedescriptors.property import Lazy
from cybertools.typology.interfaces import IType
from loops.browser.concept import ConceptView
+from loops.common import adapted
class ExternalCollectionView(ConceptView):
@@ -40,9 +41,8 @@ class ExternalCollectionView(ConceptView):
def update(self):
if 'update' in self.request.form:
- ti = IType(self.context).typeInterface
- if ti is not None:
- adapted = ti(self.context)
- adapted.update()
+ cta = adapted(self.context)
+ if cta is not None:
+ cta.update()
return True
diff --git a/integrator/collection.py b/integrator/collection.py
index f125a67..0adfd8f 100644
--- a/integrator/collection.py
+++ b/integrator/collection.py
@@ -156,4 +156,6 @@ class DirectoryCollectionProvider(object):
base, ext = title.rsplit('.', 1)
if ext.lower() in mimetypes.extensions.values():
title = base
- return title.decode('UTF-8')
+ if not isinstance(title, unicode):
+ title = title.decode('UTF-8')
+ return title
diff --git a/interfaces.py b/interfaces.py
index 8c7fda9..388ee66 100644
--- a/interfaces.py
+++ b/interfaces.py
@@ -42,12 +42,20 @@ class ILoopsObject(Interface):
""" Common top-level interface.
"""
+ title = Attribute(u'A short line of information about an object to be '
+ 'used e.g. for menu items or listing entries.')
+
def getLoopsRoot():
""" Return the loops root object.
"""
- title = Attribute(u'A short line of information about an object to be '
- 'used e.g. for menu items or listing entries.')
+ def getAllParents(collectGrants=False):
+ """ Return a sequence (or an ordered mapping / Jeep object)
+ with informations about all parents of the object.
+
+ If ``collectGrants`` is set also collect grant information
+ (assigned/effective roles) together with the parents.
+ """
class IPotentialTarget(Interface):
diff --git a/resource.py b/resource.py
index 9b9bf87..fe17ba0 100644
--- a/resource.py
+++ b/resource.py
@@ -49,7 +49,11 @@ from cybertools.relation.interfaces import IRelatable
from cybertools.storage.interfaces import IExternalStorage
from cybertools.text.interfaces import ITextTransform
from cybertools.typology.interfaces import IType, ITypeManager
+from cybertools.util.jeep import Jeep
+from loops.base import ParentInfo
+from loops.common import ResourceAdapterBase, adapted
+from loops.concept import ResourceRelation
from loops.interfaces import IBaseResource, IResource
from loops.interfaces import IFile, IExternalFile, INote
from loops.interfaces import IDocument, ITextDocument, IDocumentSchema, IDocumentView
@@ -58,8 +62,7 @@ from loops.interfaces import IResourceManager, IResourceManagerContained
from loops.interfaces import ITypeConcept
from loops.interfaces import ILoopsContained
from loops.interfaces import IIndexAttributes
-from loops.concept import ResourceRelation
-from loops.common import ResourceAdapterBase, adapted
+from loops import util
from loops.versioning.util import getMaster
from loops.view import TargetRelation
@@ -76,6 +79,9 @@ class ResourceManager(BTreeContainer):
def getViewManager(self):
return self.getLoopsRoot().getViewManager()
+ def getAllParents(self, collectGrants=False):
+ return Jeep()
+
class Resource(Image, Contained):
@@ -161,6 +167,17 @@ class Resource(Image, Contained):
def getLoopsRoot(self):
return zapi.getParent(self).getLoopsRoot()
+ def getAllParents(self, collectGrants=False):
+ result = Jeep()
+ for rel in self.getConceptRelations():
+ obj = rel.first
+ uid = util.getUidForObject(obj)
+ pi = result.setdefault(uid, ParentInfo(obj))
+ if rel not in pi.relations:
+ pi.relations.append(rel)
+ obj.getAllParents(collectGrants, result)
+ return result
+
# concept relations
# note: we always use the master version for relations, see getMaster()
diff --git a/view.py b/view.py
index e49e116..88e2c4a 100644
--- a/view.py
+++ b/view.py
@@ -39,13 +39,16 @@ from persistent import Persistent
from cybertools.relation import DyadicRelation
from cybertools.relation.registry import getRelations
from cybertools.relation.interfaces import IRelationRegistry, IRelatable
+from cybertools.util.jeep import Jeep
+from loops.base import ParentInfo
from loops.interfaces import IView, INode
from loops.interfaces import IViewManager, INodeContained
from loops.interfaces import ILoopsContained
from loops.interfaces import ITargetRelation
from loops.interfaces import IConcept
from loops.versioning.util import getVersion
+from loops import util
class View(object):
@@ -103,6 +106,9 @@ class View(object):
def getLoopsRoot(self):
return zapi.getParent(self).getLoopsRoot()
+ def getAllParents(self, collectGrants=False):
+ return Jeep()
+
class Node(View, OrderedContainer):
@@ -128,6 +134,14 @@ class Node(View, OrderedContainer):
parent = zapi.getParent(parent)
return None
+ def getAllParents(self, collectGrants=False):
+ result = Jeep()
+ parent = self.getParentNode()
+ while parent is not None:
+ result[util.getUidForObject(parent)] = ParentInfo(parent)
+ parent = parent.getParentNode()
+ return result
+
def getChildNodes(self, nodeTypes=None):
for item in self.values():
if INode.providedBy(item) \
@@ -170,6 +184,9 @@ class ViewManager(OrderedContainer):
def getViewManager(self):
return self
+ def getAllParents(self, collectGrants=False):
+ return Jeep()
+
class TargetRelation(DyadicRelation):
""" A relation between a view and its target.