=============================================================== 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 import interface, component >>> 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'] >>> from loops.view import ViewManager, Node >>> loopsRoot['views'] = ViewManager() >>> views = loopsRoot['views'] 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' resources, >>> doc1 = Document(u'Zope Info') >>> resources['doc1'] = doc1 >>> doc1.title u'Zope Info' >>> img1 = MediaAsset(u'An Image') >>> resources['img1'] = img1 and nodes (in view space): >>> m1 = Node(u'Home') >>> views['m1'] = m1 >>> m1.nodeType = 'menu' >>> m1p1 = Node(u'Page') >>> m1['p1'] = m1p1 >>> m1p1.nodeType = 'page' 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' >>> type_type.qualifiers ('concept',) 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 >>> topic_type.qualifiers ('concept',) >>> topic_type.defaultContainer >>> topic_type.factory Now let's have a look at resources. >>> from loops.interfaces import IResource >>> from loops.type import ResourceType >>> ztapi.provideAdapter(IResource, IType, ResourceType) >>> doc1_type = IType(doc1) >>> doc1_type.title u'Document' >>> doc1_type.token 'loops.resource.Document' >>> doc1_type.tokenForSearch 'loops:resource:document' >>> doc1_type.qualifiers ('resource',) >>> doc1_type.defaultContainer >>> doc1_type.factory >>> img1_type = IType(img1) >>> img1_type.title u'Media Asset' 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', 'loops.resource.Document', 'loops.resource.MediaAsset'] >>> typeManager.getType('.loops/concepts/topic') == cc1_type True The listTypes() method allows us to select types that fulfill a certain condition: >>> types = typeManager.listTypes(include=('concept',)) >>> sorted(t.token for t in types) ['.loops/concepts/topic', '.loops/concepts/type'] >>> types = typeManager.listTypes(exclude=('concept',)) >>> sorted(t.token for t in types) ['loops.resource.Document', 'loops.resource.MediaAsset'] Type-based interfaces and adapters ---------------------------------- >>> from loops.interfaces import ITypeConcept >>> from loops.type import TypeConcept >>> ztapi.provideAdapter(IConcept, ITypeConcept, TypeConcept) A type has an optional typeInterface attribute that objects of this type will be adaptable to. The default for this is None: >>> cc1_type.typeInterface is None True For concept objects that provide types (type providers) the value of the typeInterface attribute is the ITypeConcept interface: >>> topic_type.typeInterface is ITypeConcept True We now want to have a topic (i.e. a concept that has topic as its conceptType and thus topic_type as its type) to be adaptable to ITopic. This is done by assigning this interface to topic_type.typeProvider, i.e. the 'topic' concept, via an adapter: >>> class ITopic(Interface): pass >>> from zope.interface import implements >>> class Topic(object): ... implements(ITopic) ... def __init__(self, context): pass >>> component.provideAdapter(Topic, (IConcept,), ITopic) >>> ITypeConcept(topic).typeInterface = ITopic >>> cc1.conceptType = topic >>> cc1_type = IType(cc1) >>> cc1Adapter = cc1_type.typeInterface(cc1) >>> ITopic.providedBy(cc1Adapter) True Concepts as queries ------------------- We first have to set up the query type, i.e. a type concept associated with the IQueryConcept interface: >>> from loops.query import IQueryConcept, QueryConcept >>> component.provideAdapter(QueryConcept, (IConcept,), IQueryConcept) >>> query = concepts['query'] = Concept(u'Query') >>> query.conceptType = typeObject >>> ITypeConcept(query).typeInterface = IQueryConcept Next we need a concept of this type: >>> simpleQuery = concepts['simpleQuery'] = Concept(u'Simple query') >>> simpleQuery.conceptType = query >>> sq_type = IType(simpleQuery) >>> sq_adapter = sq_type.typeInterface(simpleQuery) >>> sq_adapter.viewName = 'simpleview.html' >>> simpleQuery._viewName 'simpleview.html' This viewName attribute of the query will be automatically used by a concept view when asked for the view that should be used for rendering the concept... >>> from loops.browser.concept import ConceptView >>> from zope.publisher.browser import TestRequest >>> sq_baseView = ConceptView(simpleQuery, TestRequest()) >>> sq_view = sq_baseView.view ...but only when the view exists, i.e. there is a class registered as a view/multi-adapter with this name: >>> sq_view is sq_baseView True >>> class SimpleView(object): ... def __init__(self, context, request): pass >>> from zope.publisher.interfaces.browser import IBrowserRequest >>> component.provideAdapter(SimpleView, (IConcept, IBrowserRequest), Interface, ... name='simpleview.html') >>> sq_baseView = ConceptView(simpleQuery, TestRequest()) >>> sq_view = sq_baseView.view >>> sq_view is sq_baseView False >>> sq_view.__class__ Controlling presentation using view properties ---------------------------------------------- >>> from zope.app.annotation.interfaces import IAttributeAnnotatable, IAnnotations >>> from zope.app.annotation.attribute import AttributeAnnotations >>> from loops.interfaces import INode First we have to make sure we can use attribute annotations with our nodes, and we also have to register an IViewConfigurator adapter for them: >>> component.provideAdapter(AttributeAnnotations, (INode,), IAnnotations) >>> from cybertools.browser.configurator import IViewConfigurator >>> from loops.browser.node import NodeViewConfigurator >>> from zope.publisher.interfaces.browser import IBrowserRequest >>> component.provideAdapter(NodeViewConfigurator, (INode, IBrowserRequest), ... IViewConfigurator) Now we are ready to set up a view on our page node: >>> from loops.browser.node import NodeView >>> request = TestRequest() >>> view = NodeView(m1p1, request) The elements responsible for presentation are controlled by a controller object: >>> from cybertools.browser.controller import Controller >>> controller = Controller(view, request) >>> getattr(controller, 'skinName', None) is None True There is no `skinName` setting in the controller as we did not set any. The configurator (IViewConfigurator adapter, see above) takes the view properties from the attribute annotations. We set these properties using an adapter to the config schema; the configurator will only use settings on menu nodes (possibly above the node to be viewed in the browser). >>> from loops.interfaces import IViewConfiguratorSchema >>> from loops.browser.node import ViewPropertiesConfigurator >>> component.provideAdapter(ViewPropertiesConfigurator, (INode,), ... IViewConfiguratorSchema) >>> pageConfigurator = IViewConfiguratorSchema(m1) >>> pageConfigurator.skinName = 'SuperSkin' >>> controller = Controller(view, request) >>> controller.skinName.value 'SuperSkin'