loops/helpers.txt
helmutm 6e3e08c781 provide system qualifier for type type and predicate, check it when searching; + some minor refactorings
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1605 fd906abe-77d9-0310-91a1-e0d9ade77398
2007-03-02 10:48:06 +00:00

417 lines
13 KiB
Text
Executable file

===============================================================
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.interface import Interface
>>> from zope.publisher.browser import TestRequest
and provide a relation registry:
>>> from cybertools.relation.interfaces import IRelationRegistry
>>> from cybertools.relation.registry import DummyRelationRegistry
>>> component.provideUtility(DummyRelationRegistry())
and care for some type adapter machinery:
>>> from loops.interfaces import IConcept
>>> from loops.interfaces import ITypeConcept
>>> from loops.type import TypeConcept
>>> component.provideAdapter(TypeConcept, (IConcept,), ITypeConcept)
Now we can setup a simple loops site with its manager objects, using a
loops setup manager:
>>> from loops import Loops
>>> loopsRoot = site['loops'] = Loops()
>>> from loops.setup import SetupManager
>>> setup = SetupManager(loopsRoot)
>>> concepts, resources, views = setup.setup()
>>> concepts['hasType'].title
u'has Type'
We also add some example concepts,
>>> from loops.concept import Concept
>>> 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,
>>> from loops.resource import Resource
>>> file1 = resources['file1'] = Resource(u'A file')
>>> file1.resourceType = concepts['file']
(the use of Document may get deprecated soon:)
>>> from loops.resource import Document
>>> doc1 = Document(u'Zope Info')
>>> resources['doc1'] = doc1
>>> doc1.title
u'Zope Info'
>>> img1 = resources['img1'] = Resource(u'An Image')
>>> img1.resourceType = concepts['file']
and nodes (in view space):
>>> from loops.view import Node
>>> m1 = Node(u'Home')
>>> views['m1'] = m1
>>> m1.nodeType = 'menu'
>>> m1p1 = Node(u'Page')
>>> m1['p1'] = m1p1
>>> m1p1.nodeType = 'page'
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.type import ConceptType
>>> component.provideAdapter(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'
During setup we created two special concepts: one ('hasType') as the predicate
signifying a type relation, and the other ('type') as the one and only type
concept:
>>> 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', 'system')
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', 'system')
>>> topic_type.defaultContainer
<loops.concept.ConceptManager object ...>
>>> topic_type.factory
<class 'loops.concept.Concept'>
Now let's have a look at resources.
>>> from loops.interfaces import IResource
>>> from loops.type import LoopsType
>>> component.provideAdapter(LoopsType, (IResource,), IType)
>>> file1_type = IType(file1)
>>> file1_type.title
u'File'
>>> file1_type.token
'.loops/concepts/file'
>>> file1_type.tokenForSearch
'loops:resource:file'
>>> file1_type.qualifiers
('resource',)
>>> file1_type.defaultContainer
<loops.resource.ResourceManager object ...>
>>> file1_type.factory
<class 'loops.resource.Resource'>
(The use of Document will be deprecated soon...)
>>> from loops.interfaces import IResource, IDocument
>>> from loops.type import ResourceType
>>> component.provideAdapter(ResourceType, (IDocument,), IType)
>>> 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
<loops.resource.ResourceManager object ...>
>>> doc1_type.factory
<class 'loops.resource.Document'>
>>> img1_type = IType(img1)
>>> img1_type.token
'.loops/concepts/file'
>>> img1_type.tokenForSearch
'loops:resource:file'
>>> img1_type.title
u'File'
Using the type machinery we can also specify options that may be used
for controlling e.g. storage for external files.
>>> extfile = concepts['extfile'] = Concept(u'External File')
>>> ef1 = resources['ef1'] = Resource(u'Extfile #1')
>>> ef1.resourceType = extfile
>>> ef1_type = IType(ef1)
>>> IType(ef1).options
[]
>>> extfile_ad = TypeConcept(extfile)
>>> extfile_ad.options = ['dummy', 'storage:varsubdir',
... 'storage_parameters:extfiles']
>>> IType(ef1).options
['dummy', 'storage:varsubdir', 'storage_parameters:extfiles']
>>> IType(ef1).optionsDict
{'default': ['dummy'], 'storage_parameters': 'extfiles', 'storage': 'varsubdir'}
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
>>> component.provideAdapter(LoopsTypeManager)
>>> typeManager = ITypeManager(loopsRoot)
>>> typeManager.context == ITypeManager(cc1).context == loopsRoot
True
>>> types = typeManager.types
>>> sorted(t.token for t in types)
['.loops/concepts/file', '.loops/concepts/image', '.loops/concepts/predicate',
'.loops/concepts/textdocument', '.loops/concepts/topic',
'.loops/concepts/type',
'loops.resource.Document']
>>> 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/predicate', '.loops/concepts/topic', '.loops/concepts/type']
>>> types = typeManager.listTypes(exclude=('concept',))
>>> sorted(t.token for t in types)
['.loops/concepts/file', '.loops/concepts/image',
'.loops/concepts/textdocument', 'loops.resource.Document']
Type-based interfaces and adapters
----------------------------------
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
Simple access to type information with BaseView
-----------------------------------------------
loops browser views are typically based on a common parent class, BaseView.
BaseView provides simple access to a lot of information often needed for
browser views; among others also some important informations about the
context object's type:
>>> from loops.browser.common import BaseView
>>> view = BaseView(cc1, TestRequest)
>>> view.typeTitle
u'Topic'
>>> view.typeInterface
<InterfaceClass ...ITopic>
>>> view.typeAdapter
<Topic object ...>
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__
<class 'SimpleView'>
Controlling presentation using view properties
----------------------------------------------
>>> from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations
>>> from zope.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; note that we have to provide a named template 'loops.node_macros' that
is used to retrieve a macro used by NodeView:
>>> from cybertools.browser.controller import Controller
>>> from loops.browser.util import node_macros
>>> from loops.browser.common import BaseView
>>> component.provideAdapter(node_macros, (BaseView,), name='loops.node_macros')
>>> 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'