loops/helpers.txt
helmutm d1dbe47d57 move QueryConcept stuff to loops.expert (keeping old query module for backward compatibility); work in progress: child-based queries with actions
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2935 fd906abe-77d9-0310-91a1-e0d9ade77398
2008-10-23 13:35:23 +00:00

471 lines
15 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 import interface, component
>>> from zope.interface import Interface
>>> from zope.publisher.browser import TestRequest
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
>>> site = placefulSetUp(True)
>>> from loops.tests.setup import TestSite
>>> t = TestSite(site)
>>> concepts, resources, views = t.setup()
>>> concepts['hasType'].title
u'has Type'
>>> loopsRoot = site['loops']
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']
(note: the use of Document is deprecated)
>>> 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 is deprecated!)
>>> 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.
>>> from loops.interfaces import IFile
>>> from loops.resource import FileAdapter
>>> component.provideAdapter(FileAdapter, provides=IFile)
>>> extfile = concepts['extfile'] = Concept(u'External File')
>>> ef1 = resources['ef1'] = Resource(u'Extfile #1')
>>> ef1.resourceType = extfile
>>> ef1_type = IType(ef1)
>>> IType(ef1).options
[]
>>> from loops.type import TypeConcept
>>> 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
>>> typeTokens = sorted(t.token for t in types)
>>> len(typeTokens)
8
>>> 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',))
>>> typeTokens = sorted(t.token for t in types)
>>> len(typeTokens)
5
>>> types = typeManager.listTypes(exclude=('concept',))
>>> typeTokens = sorted(t.token for t in types)
>>> len(typeTokens)
3
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:
>>> from loops.interfaces import ITypeConcept
>>> 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
>>> from loops.interfaces import IConcept
>>> 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
There is a shortcut to getting the typeInterface adapter for an object.
>>> from loops.common import adapted
>>> cc1Adapter = adapted(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. The query type concept itself has already
been provided by the setup, but we have to register a corresponding adapter.
>>> from loops.expert.concept import IQueryConcept, QueryConcept
>>> component.provideAdapter(QueryConcept)
>>> from loops.setup import addAndConfigureObject
>>> query = addAndConfigureObject(concepts, Concept,
... 'query', conceptType=typeObject, 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.
As the display of the standard macros is controlled by permissions we have
to install a checker first.
>>> 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'
Folders
=======
We may provide a concept type called a folder - there is no special
functionality about it but it may be used for building a pseudo hierarchy
using nested folders. This may make it easier for users to map the
structures of their documents in the filesystem to the loops concept
map.
>>> tFolder = addAndConfigureObject(concepts, Concept, 'folder',
... title=u'Folder', conceptType=typeObject)
Usually we want to create folders only in objects of a certain type,
e.g. in a domain. So we activate the folder creation action by providing
the domain type with a corresponding option.
>>> tDomain = concepts['domain']
>>> taDomain = adapted(tDomain)
>>> taDomain.options = ['action.portlet:createFolder']
Importing the FolderView will register this action.
>>> from loops.browser.folder import FolderView
If we now create a domain and set up a view on it it will provide the
folder creation action.
>>> general = addAndConfigureObject(concepts, Concept, 'general',
... title=u'General', conceptType=tDomain)
>>> from loops.browser.concept import ConceptView
>>> view = ConceptView(general, TestRequest())
>>> sorted(a.name for a in view.getActions('portlet'))
['createFolder']
Let's now create a folder.
>>> f01 = addAndConfigureObject(concepts, Concept, 'f01',
... title=u'Test Folder', conceptType=tFolder)
A folder should be associated with a FolderView that provides two actions
for editing the folder and for creating a new subfolder.
>>> view = FolderView(f01, TestRequest())
>>> sorted(a.name for a in view.getActions('portlet'))
['createFolder', 'editFolder']
Fin de partie
=============
>>> placefulTearDown()