diff --git a/README.txt b/README.txt
index defb60b..b10d998 100755
--- a/README.txt
+++ b/README.txt
@@ -4,6 +4,17 @@ loops - Linked Objects for Organization and Processing Services
($Id$)
+The loops platform is built up of three parts:
+
+(1) concepts: simple interconnected objects usually representing
+ meta-information
+(2) resources: (possibly large) atomic objects like documents and media assets
+(3) views: objects (usually hierarchically organized nodes) providing
+ access to and presenting concepts or resources
+
+Note that there is another doctest file called helpers.txt that deals
+with lower-level aspects like type or state management.
+
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
>>> site = placefulSetUp(True)
diff --git a/concept.py b/concept.py
index f06fbc1..4300cd1 100644
--- a/concept.py
+++ b/concept.py
@@ -88,22 +88,21 @@ class Concept(Contained, Persistent):
def getConceptType(self):
typePred = self.getConceptManager().getTypePredicate()
+ if typePred is None:
+ return None
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):
current = self.getConceptType()
if current != concept:
typePred = self.getConceptManager().getTypePredicate()
+ if typePred is None:
+ raise ValueError('No type predicate found for '
+ + zapi.getName(self))
if current is not None:
self.deassignParent(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''):
@@ -196,9 +195,7 @@ class ConceptManager(BTreeContainer):
return zapi.getParent(self)
def getTypePredicate(self):
- if self.typePredicate is None:
- self.typePredicate = self['hasType']
- return self.typePredicate
+ return self.get('hasType')
def getTypeConcept(self):
if self.typeConcept is None:
diff --git a/configure.zcml b/configure.zcml
index 128d3a2..b50a3a4 100644
--- a/configure.zcml
+++ b/configure.zcml
@@ -234,6 +234,10 @@
+
+
+
+
diff --git a/helpers.txt b/helpers.txt
new file mode 100755
index 0000000..ba76cf9
--- /dev/null
+++ b/helpers.txt
@@ -0,0 +1,162 @@
+===============================================================
+loops - Linked Objects for Organization and Processing Services
+===============================================================
+
+This file documents and tests a bunch of facilities that support the
+loops framework, mostly provided by sub-packages of the cybertools
+package.
+
+ ($Id$)
+
+Let's first do some basic imports
+
+ >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
+ >>> site = placefulSetUp(True)
+
+ >>> from zope.app import zapi
+ >>> from zope.app.tests import ztapi
+ >>> from zope.interface import Interface
+ >>> from zope.publisher.browser import TestRequest
+
+and setup a simple loops site with its manager objects,
+
+ >>> from loops import Loops
+ >>> site['loops'] = Loops()
+ >>> loopsRoot = site['loops']
+
+ >>> from loops.concept import ConceptManager, Concept
+ >>> loopsRoot['concepts'] = ConceptManager()
+ >>> concepts = loopsRoot['concepts']
+
+ >>> from loops.resource import ResourceManager, Document, MediaAsset
+ >>> loopsRoot['resources'] = ResourceManager()
+ >>> resources = loopsRoot['resources']
+
+some concepts,
+
+ >>> cc1 = Concept(u'Zope')
+ >>> concepts['cc1'] = cc1
+ >>> cc1.title
+ u'Zope'
+
+ >>> cc2 = Concept(u'Zope 3')
+ >>> concepts['cc2'] = cc2
+ >>> cc2.title
+ u'Zope 3'
+
+and resources:
+
+ >>> doc1 = Document(u'Zope Info')
+ >>> resources['doc1'] = doc1
+ >>> doc1.title
+ u'Zope Info'
+
+ >>> img1 = MediaAsset(u'An Image')
+ >>> resources['img1'] = img1
+
+Finally, we also need a relation registry:
+
+ >>> from cybertools.relation.interfaces import IRelationRegistry
+ >>> from cybertools.relation.registry import DummyRelationRegistry
+ >>> from zope.app.testing import ztapi
+ >>> ztapi.provideUtility(IRelationRegistry, DummyRelationRegistry())
+
+Type management with typology
+=============================
+
+The type of an object may be determined by adapting it to the IType
+interface. The loops framework provides an adapter (LoopsType) for this
+purpose:
+
+ >>> from cybertools.typology.interfaces import IType
+ >>> from loops.interfaces import IConcept
+ >>> from loops.type import ConceptType
+ >>> ztapi.provideAdapter(IConcept, IType, ConceptType)
+ >>> cc1_type = IType(cc1)
+
+As we have not yet associated a type with one of our content objects we get
+
+ >>> cc1_type.typeProvider is None
+ True
+ >>> cc1_type.title
+ u'Unknown Type'
+ >>> cc1_type.token
+ '.unknown'
+
+So we create two special concepts: one ('hasType') as the predicate signifying
+a type relation, and the other ('type') as the one and only type concept:
+
+ >>> concepts['hasType'] = Concept(u'has type')
+ >>> concepts['type'] = Concept(u'Type')
+ >>> typeObject = concepts['type']
+
+Assigning a type to a concept is a core functionality of concepts as
+concept types are themselves concepts; and we assign the type object to
+itself (as it in fact is of type 'type'):
+
+ >>> typeObject.conceptType = typeObject
+
+So let's check the type of the type object:
+
+ >>> type_type = IType(typeObject)
+ >>> type_type.typeProvider is typeObject
+ True
+ >>> type_type.title
+ u'Type'
+ >>> type_type.token
+ '.loops/concepts/type'
+ >>> type_type.tokenForSearch
+ 'loops:concept:type'
+
+Now we register another type ('topic') and assign it to cc1:
+
+ >>> concepts['topic'] = Concept(u'Topic')
+ >>> topic = concepts['topic']
+ >>> topic.conceptType = typeObject
+ >>> cc1.conceptType = topic
+
+Note: as these kind of usually short-living adapters makes heavy use of
+lazy properties, one should always get a new adapter:
+
+ >>> topic_type = IType(topic)
+ >>> cc1_type = IType(cc1)
+ >>> topic_type == type_type
+ True
+ >>> cc1_type == topic_type
+ False
+ >>> cc1_type.typeProvider == topic
+ True
+
+Can we find out somehow which types are available? This is the time to look
+for a type manager. This could be a utility; but in the loops package it
+is again an adapter, now for the loops root object. Nevertheless one can
+get a type manager from all loops objects, always with the same context:
+
+ >>> from cybertools.typology.interfaces import ITypeManager
+ >>> from loops.interfaces import ILoopsObject
+ >>> from loops.type import LoopsTypeManager
+ >>> ztapi.provideAdapter(ILoopsObject, ITypeManager, LoopsTypeManager)
+ >>> typeManager = ITypeManager(loopsRoot)
+ >>> typeManager.context == ITypeManager(cc1).context == loopsRoot
+ True
+
+ >>> types = typeManager.types
+ >>> sorted(t.token for t in types)
+ ['.loops/concepts/topic', '.loops/concepts/type']
+
+ >>> typeManager.getType('.loops/concepts/topic') == cc1_type
+ True
+
+Index attributes adapter
+------------------------
+
+ >>> from loops.concept import IndexAttributes
+ >>> idx = IndexAttributes(cc2)
+ >>> idx.type()
+ 'loops:concept:unknown'
+
+ >>> from loops.resource import IndexAttributes
+ >>> idx = IndexAttributes(doc1)
+ >>> idx.type()
+ 'loops:resource:Document'
+
diff --git a/interfaces.py b/interfaces.py
index a647f74..7c01066 100644
--- a/interfaces.py
+++ b/interfaces.py
@@ -476,6 +476,20 @@ class IConceptRelation(IRelation):
"""
+# type and type manager interfaces - probably obsolete
+
+# class ILoopsType(IType):
+# """ Each loops object is of a certain type providing this interface.
+# Usually implemented as an adapter.
+# """
+
+
+# class ILoopsTypeManager(ITypeManager):
+# """ The loops type manager, probably implemented by an adapter to
+# the loops root object or the loops root object itself.
+# """
+
+
# interfaces for catalog indexes
class IIndexAttributes(Interface):
diff --git a/resource.py b/resource.py
index 1b6f72d..f5cf8d6 100644
--- a/resource.py
+++ b/resource.py
@@ -31,6 +31,9 @@ from zope.i18nmessageid import MessageFactory
from zope.interface import implements
from persistent import Persistent
from cStringIO import StringIO
+
+from textindexng.interfaces import IIndexableContent
+from textindexng.content import IndexContentCollector
from cybertools.relation.registry import getRelations
from cybertools.relation.interfaces import IRelatable
@@ -163,6 +166,22 @@ class IndexAttributes(object):
return ':'.join(('loops:resource', context.__class__.__name__))
+class IndexableResource(object):
+
+ implements(IIndexableContent)
+ adapts(IResource)
+
+ def __init__(self, context):
+ self.context = context
+
+ def indexableContent(self, fields):
+ context = self.context
+ icc = IndexContentCollector()
+ icc.addBinary(fields[0], context.data, context.contentType, language='de')
+ return icc
+
+
+
def getResourceTypes():
return (('loops.resource.Document', _(u'Document')),
('loops.resource.MediaAsset', _(u'Media Asset')),
diff --git a/tests/test_loops.py b/tests/test_loops.py
index 503ec23..330425d 100755
--- a/tests/test_loops.py
+++ b/tests/test_loops.py
@@ -44,6 +44,7 @@ def test_suite():
return unittest.TestSuite((
unittest.makeSuite(Test),
DocFileSuite('../README.txt', optionflags=flags),
+ DocFileSuite('../helpers.txt', optionflags=flags),
))
if __name__ == '__main__':
diff --git a/type.py b/type.py
new file mode 100644
index 0000000..736a87e
--- /dev/null
+++ b/type.py
@@ -0,0 +1,93 @@
+#
+# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+Type management stuff.
+
+$Id$
+"""
+
+from zope.app import zapi
+from zope.component import adapts
+from zope.cachedescriptors.property import Lazy
+from cybertools.typology.type import BaseType, TypeManager
+from loops.interfaces import ILoopsObject, IConcept
+
+
+class LoopsType(BaseType):
+
+ @Lazy
+ def title(self):
+ tp = self.typeProvider
+ return tp is None and u'Unknown Type' or tp.title
+
+ @Lazy
+ def token(self):
+ tp = self.typeProvider
+ return tp is None and '.unknown' or self.root.getLoopsUri(tp)
+
+ @Lazy
+ def tokenForSearch(self):
+ tp = self.typeProvider
+ typeName = tp is None and 'unknown' or str(zapi.getName(tp))
+ return ':'.join(('loops:concept', typeName,))
+
+ @Lazy
+ def root(self):
+ return self.context.getLoopsRoot()
+
+
+class LoopsTypeInfo(LoopsType):
+ """ A common class the instances of which are used as a generic type info.
+ """
+
+ def __init__(self, typeProvider):
+ self.typeProvider = self.context = typeProvider
+
+
+class ConceptType(LoopsType):
+ """ The type adapter for concept objects.
+ """
+
+ adapts(IConcept)
+
+ @Lazy
+ def typeProvider(self):
+ return self.context.conceptType
+
+
+class LoopsTypeManager(TypeManager):
+
+ adapts(ILoopsObject)
+
+ def __init__(self, context):
+ self.context = context.getLoopsRoot()
+
+ @property
+ def types(self):
+ cm = self.context.getConceptManager()
+ to = cm.getTypeConcept()
+ tp = cm.getTypePredicate()
+ if to is None or tp is None:
+ return ()
+ result = to.getChildren([tp])
+ if to not in result:
+ result.append(to)
+ return tuple(LoopsTypeInfo(c) for c in result)
+
+