From dfd398a401189007685ca68e8d951402ce8654e4 Mon Sep 17 00:00:00 2001 From: helmutm Date: Wed, 8 Mar 2006 15:24:40 +0000 Subject: [PATCH] Provide typology stuff for concepts git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1119 fd906abe-77d9-0310-91a1-e0d9ade77398 --- README.txt | 11 +++ concept.py | 15 ++-- configure.zcml | 4 ++ helpers.txt | 162 ++++++++++++++++++++++++++++++++++++++++++++ interfaces.py | 14 ++++ resource.py | 19 ++++++ tests/test_loops.py | 1 + type.py | 93 +++++++++++++++++++++++++ 8 files changed, 310 insertions(+), 9 deletions(-) create mode 100755 helpers.txt create mode 100644 type.py 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) + +