No description
Find a file
helmutm ef597268ec Renamed *RelationsRegistry* to *RelationRegistry*
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1066 fd906abe-77d9-0310-91a1-e0d9ade77398
2006-02-09 14:05:51 +00:00
browser More on assigning targets to views/nodes via a vocabulary, including doc tests 2006-02-06 15:14:04 +00:00
tests Improvements on resource.MediaAsset; moved tests to directory in order to place files there for test upload 2006-01-20 12:14:46 +00:00
__init__.py More on assigning targets to views/nodes via a vocabulary, including doc tests 2006-02-06 15:14:04 +00:00
concept.py Renamed *RelationsRegistry* to *RelationRegistry* 2006-02-09 14:05:51 +00:00
configure.zcml Some refactoring concerning node configuration and target assignment 2006-02-04 20:38:30 +00:00
external.py Defined adapters for export/import of nodes 2006-01-16 19:09:34 +00:00
interfaces.py More on assigning targets to views/nodes via a vocabulary, including doc tests 2006-02-06 15:14:04 +00:00
README.txt Renamed *RelationsRegistry* to *RelationRegistry* 2006-02-09 14:05:51 +00:00
resource.py More on assigning targets to views/nodes via a vocabulary, including doc tests 2006-02-06 15:14:04 +00:00
target.py More on assigning targets to views/nodes via a vocabulary, including doc tests 2006-02-06 15:14:04 +00:00
util.py Some refactoring concerning node configuration and target assignment 2006-02-04 20:38:30 +00:00
view.py Renamed *RelationsRegistry* to *RelationRegistry* 2006-02-09 14:05:51 +00:00

===============================================================
loops - Linked Objects for Organization and Processing Services
===============================================================

  ($Id$)

  >>> 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
  

Concepts and Relations
======================

Let's start with creating a few example concepts, putting them in a
top-level loops container and a concept manager:

  >>> from loops import Loops
  >>> site['loops'] = Loops()
  >>> loopsRoot = site['loops']

  >>> from loops.concept import ConceptManager, Concept
  >>> loopsRoot['concepts'] = ConceptManager()
  >>> concepts = loopsRoot['concepts']
  >>> cc1 = Concept()
  >>> concepts['cc1'] = cc1
  >>> cc1.title
  u''
  >>> loopsRoot.getLoopsUri(cc1)
  '.loops/concepts/cc1'

  >>> cc2 = Concept(u'Zope 3')
  >>> concepts['cc2'] = cc2
  >>> cc2.title
  u'Zope 3'

Now we want to relate the second concept to the first one.

In order to do this we first have to provide a relation registry. For
testing we use a simple dummy implementation.

  >>> from cybertools.relation.interfaces import IRelationRegistry
  >>> from cybertools.relation.registry import DummyRelationRegistry
  >>> from zope.app.testing import ztapi
  >>> ztapi.provideUtility(IRelationRegistry, DummyRelationRegistry())

Now we can assign the concept c2 to c1 (using the standard ConceptRelation):
        
  >>> cc1.assignConcept(cc2)

We can now ask our concepts for their related concepts:

  >>> sc1 = cc1.getSubConcepts()
  >>> len(sc1)
  1
  >>> cc2 in sc1
  True
  >>> len(cc1.getParentConcepts())
  0

  >>> pc2 = cc2.getParentConcepts()
  >>> len(pc2)
  1

  >>> cc1 in pc2
  True
  >>> len(cc2.getSubConcepts())
  0

TODO: Work with views...
        

Resources and what they have to do with Concepts
================================================

  >>> from loops.interfaces import IDocument, IMediaAsset

We first need a resource manager:
    
  >>> from loops.resource import ResourceManager
  >>> loopsRoot['resources'] = ResourceManager()
  >>> resources = loopsRoot['resources']

A common type of resource is a document:
      
  >>> from loops.interfaces import IDocument
  >>> from loops.resource import Document
  >>> doc1 = Document(u'Zope Info')
  >>> resources['doc1'] = doc1
  >>> doc1.title
  u'Zope Info'
  >>> doc1.data
  u''
  >>> doc1.contentType
  ''

Another one is a media asset:

  >>> from loops.interfaces import IMediaAsset
  >>> from loops.resource import MediaAsset
  >>> img = MediaAsset(u'A png Image')

For testing we use some simple files from the tests directory:
      
  >>> from loops import tests
  >>> import os
  >>> path = os.path.join(*tests.__path__)
  >>> img.data = open(os.path.join(path, 'test_icon.png')).read()
  >>> img.getSize()
  381
  >>> img.getImageSize()
  (16, 16)
  >>> img.contentType
  'image/png'

  >>> pdf = MediaAsset(u'A pdf File')
  >>> pdf.data = open(os.path.join(path, 'test.pdf')).read()
  >>> pdf.getSize()
  25862
  >>> pdf.getImageSize()
  (-1, -1)
  >>> pdf.contentType
  'application/pdf'

We can associate a resource with a concept by assigning it to the concept:

  >>> cc1.assignResource(doc1)
  >>> res = cc1.getResources()
  >>> list(res)
  [<loops.resource.Document ...>]

The resource also provides access to the associated concepts (or views, see
below) via the getClients() method:

  >>> conc = doc1.getClients()
  >>> len(conc)
  1
  >>> conc[0] is cc1
  True


Views/Nodes: Menus, Menu Items, Listings, Pages, etc
====================================================

Note: the term "view" here is not directly related to the special
Zop 3 term "view" (a multiadapter for presentation purposes) but basically
bears the common sense meaning: an object (that may be persistent or
created on the fly) that provides a view to content of whatever kind.

Views (or nodes - that's the only type of views existing at the moment)
thus provide the presentation space to concepts and resources.

We first need a view manager:
    
  >>> from loops.view import ViewManager, Node
  >>> from zope.security.checker import NamesChecker, defineChecker
  >>> nodeChecker = NamesChecker(('body',))
  >>> defineChecker(Node, nodeChecker)

  >>> loopsRoot['views'] = ViewManager()
  >>> views = loopsRoot['views']

The view space is typically built up with nodes; a node may be a top-level
menu that may contain other nodes as menu or content items:
      
  >>> m1 = Node(u'Menu')
  >>> views['m1'] = m1
  >>> m11 = Node(u'Zope')
  >>> m1['m11'] = m11
  >>> m111 = Node(u'Zope in General')
  >>> m11['m111'] = m111
  >>> m112 = Node(u'Zope 3')
  >>> m11['m112'] = m112
  >>> m112.title
  u'Zope 3'
  >>> m112.description
  u''

There are a few convienence methods for accessing parent and child nodes:

  >>> m1.getParentNode() is None
  True
  >>> m11.getParentNode() is m1
  True
  >>> [zapi.getName(child) for child in m11.getChildNodes()]
  [u'm111', u'm112']

What is returned by these may be controlled by the nodeType attribute:

  >>> m1.nodeType = 'menu'
  >>> m11.nodeType = 'page'
  >>> m11.getParentNode('menu') is m1
  True
  >>> m11.getParentNode('page') is None
  True
  >>> m111.nodeType = 'info'
  >>> m112.nodeType = 'text'
  >>> len(list(m11.getChildNodes('text')))
  1

There are also shortcut methods to retrieve certain types of nodes
in a simple and logical way:

  >>> m1.getMenu() is m1
  True
  >>> m111.getMenu() is m1
  True
  >>> m1.getPage() is m1
  True
  >>> m111.getPage() is m111
  True
  >>> m112.getPage() is m11
  True
  >>> len(list(m1.getMenuItems()))
  1
  >>> len(list(m11.getMenuItems()))
  0
  >>> len(list(m111.getMenuItems()))
  0
  >>> len(list(m1.getTextItems()))
  0
  >>> len(list(m11.getTextItems()))
  1
  >>> len(list(m111.getTextItems()))
  0

Targets
-------

We can associate a node with a concept or directly with a resource via the
view class's target attribute. (We also have to supply a subscriber to
IRelationInvalidatedEvent to make sure associated actions will be carried
out - this is usually done through ZCML.)

  >>> from loops.util import removeTargetRelation
  >>> from loops.interfaces import ITargetRelation
  >>> from cybertools.relation.interfaces import IRelationInvalidatedEvent
  >>> ztapi.subscribe([ITargetRelation, IRelationInvalidatedEvent], None,
  ...                 removeTargetRelation)
  
  >>> m111.target = cc1
  >>> m111.target is cc1
  True
  >>> m111.target = cc1
  >>> m111.target is cc1
  True
  >>> m111.target = cc2
  >>> m111.target is cc2
  True

Node Views
----------

  >>> from loops.interfaces import INode
  >>> from loops.browser.node import NodeView
  >>> view = NodeView(m11, TestRequest())

  >>> page = view.page
  >>> items = page.textItems()
  >>> for item in items:
  ...     print item.url, item.editable
  http://127.0.0.1/loops/views/m1/m11/m112 False

  >>> menu = view.menu
  >>> items = menu.menuItems()
  >>> for item in items:
  ...     print item.url, view.selected(item)
  http://127.0.0.1/loops/views/m1/m11 True

Node Configuration
------------------

When configuring a node you may specify what you want to do with respect
to the node's target: associate an existing one or create a new one.
These options are provided via the INodeConfigSchema that is provided
by a NodeConfigAdapter; in addition the attributes of the node (like the
title) may be changed via the NodeConfigAdapter.
  
  >>> from loops.interfaces import INodeConfigSchema
  >>> from loops.view import NodeConfigAdapter
  >>> ztapi.provideAdapter(INode, INodeConfigSchema, NodeConfigAdapter)
  >>> nodeConfig = INodeConfigSchema(m111)

  >>> nodeConfig.title = u'New title for m111'
  >>> nodeConfig.title
  u'New title for m111'
  >>> m111.title
  u'New title for m111'
  >>> nodeConfig.target = doc1
  >>> m111.target is doc1
  True
  >>> m111 in doc1.getClients()
  True

The targetUri and targetType fields are only relevant when creating
a new target object:

  >>> nodeConfig.targetUri
  ''
  >>> nodeConfig.targetType
  'loops.resource.Document'

The node configuration form provides a target assignment field using
a vocabulary (source) for selecting the target. (In a future version this form
will be extended by a widget that lets you search for potential target
objects.) The source is basically a source list:

  >>> from loops.target import TargetSourceList
  >>> source = TargetSourceList(m111)
  >>> len(source)
  3
  >>> sorted([zapi.getName(s) for s in source])
  [u'cc1', u'cc2', u'doc1']

The form then uses a sort of browser view providing the ITerms interface
based on this source list:

  >>> from loops.browser.target import TargetTerms
  >>> terms = TargetTerms(source, TestRequest())
  >>> term = terms.getTerm(doc1)
  >>> term.token, term.title, term.value
  ('.loops/resources/doc1', u'Zope Info', <loops.resource.Document...>)
      
  >>> term = terms.getTerm(cc1)
  >>> term.token, term.title, term.value
  ('.loops/concepts/cc1', u'cc1', <loops.concept.Concept...>)
      
  >>> terms.getValue('.loops/concepts/cc1') is cc1
  True

There is a special edit view class that can be used to configure a node
in a way, that allows the creation of a target object on the fly.
(We here use the base class providing the method for this action; the real
application uses a subclass that does all the other stuff for form handling.)
When creating a new target object you may specify a uri that determines
the location of the new target object and its name.

  >>> from loops.browser.node import ConfigureBaseView
  >>> view = ConfigureBaseView(INodeConfigSchema(m111), TestRequest())
  >>> view.checkCreateTarget()
  >>> sorted(resources.keys())
  [u'doc1']
  >>> form = {'field.createTarget': True,
  ...         'field.targetUri': '.loops/resources/ma07',
  ...         'field.targetType': 'loops.resource.MediaAsset'}
  >>> view = ConfigureBaseView(m111, TestRequest(form=form))
  >>> m111.target = view.checkCreateTarget()
  >>> sorted(resources.keys())
  [u'doc1', u'ma07']
  >>> isinstance(resources['ma07'], MediaAsset)
  True
  
  >>> form = {'field.createTarget': True,
  ...         'field.targetType': 'loops.resource.Document'}
  >>> view = ConfigureBaseView(m111, TestRequest(form=form))
  >>> m111.target = view.checkCreateTarget()
  >>> sorted(resources.keys())
  [u'doc1', u'm1.m11.m111', u'ma07']
  >>> isinstance(resources['m1.m11.m111'], Document)
  True
        
A node object provides the targetSchema of its target:

  >>> from loops.interfaces import IDocumentView
  >>> from loops.interfaces import IMediaAssetView
  >>> IDocumentView.providedBy(m111)
  True
  >>> IMediaAssetView.providedBy(m111)
  False
  >>> m111.target = None
  >>> IDocumentView.providedBy(m111)
  False
  >>> m111.target = resources['ma07']
  >>> IDocumentView.providedBy(m111)
  False
  >>> IMediaAssetView.providedBy(m111)
  True

A node's target is rendered using the NodeView's renderTargetBody()
method. This makes use of a browser view registered for the target interface,
and of a lot of other stuff needed for the rendering machine.

  >>> from zope.app.publisher.interfaces.browser import IBrowserView
  >>> from zope.publisher.interfaces.browser import IBrowserRequest
  >>> from loops.browser.resource import DocumentView
  >>> ztapi.provideAdapter(IDocument, Interface, DocumentView,
  ...                      with=(IBrowserRequest,))

  >>> from zope.component.interfaces import IFactory
  >>> from zope.app.renderer import rest
  >>> ztapi.provideUtility(IFactory, rest.ReStructuredTextSourceFactory,
  ...                      'zope.source.rest')
  >>> ztapi.provideAdapter(rest.IReStructuredTextSource, Interface,
  ...                      rest.ReStructuredTextToHTMLRenderer,
  ...                      with=(IBrowserRequest,))

  >>> m112.target = doc1
  >>> view = NodeView(m112, TestRequest())
  >>> view.renderTargetBody()
  u''
  >>> doc1.data = u'Test data\n\nAnother paragraph'
  >>> view.renderTargetBody()
  u'Test data\n\nAnother paragraph'
  >>> doc1.contentType = 'text/restructured'
  >>> view.renderTargetBody()
  u'<p>Test data</p>\n<p>Another paragraph</p>\n'

It is possible to edit a target's attributes directly in an
edit form provided by the node:

  >>> from loops.target import DocumentProxy, MediaAssetProxy
  >>> ztapi.provideAdapter(INode, IDocumentView, DocumentProxy)
  >>> ztapi.provideAdapter(INode, IMediaAssetView, MediaAssetProxy)

  >>> proxy = zapi.getAdapter(m111, IDocumentView)
  >>> proxy.title = u'Set via proxy'
  >>> resources['ma07'].title
  u'Set via proxy'

If the target object is removed from its container all references
to it are removed as well. (To make this work we have to handle
the IObjectRemovedEvent; this is usually done via ZCML in the
cybertools.relation package.)

  >>> from zope.app.container.interfaces import IObjectRemovedEvent
  >>> from zope.interface import Interface
  >>> from cybertools.relation.registry import invalidateRelations
  >>> ztapi.subscribe([Interface, IObjectRemovedEvent], None,
  ...                 invalidateRelations)

  >>> del resources['ma07']
  >>> m111.target
  >>> IMediaAssetView.providedBy(m111)
  False

Ordering Nodes
--------------

Let's add some more nodes and reorder them:

  >>> m113 = Node()
  >>> m11['m113'] = m113
  >>> m114 = Node()
  >>> m11['m114'] = m114
  >>> m11.keys()
  ['m111', 'm112', 'm113', 'm114']
      
A special management view provides methods for moving objects down, up,
to the bottom, and to the top.
      
  >>> from cybertools.container.ordered import OrderedContainerView
  >>> view = OrderedContainerView(m11, TestRequest())
  >>> view.move_bottom(('m113',))
  >>> m11.keys()
  ['m111', 'm112', 'm114', 'm113']
  >>> view.move_up(('m114',), 1)
  >>> m11.keys()
  ['m111', 'm114', 'm112', 'm113']

Import/Export
-------------

Nodes may be exported to and loaded from external sources, typically
file representations that allow the transfer of nodes from one Zope
instance to another.

  >>> from loops.external import NodesLoader
  >>> loader = NodesLoader(views)
  >>> data = [{'name': 'm2', 'path': '', 'description': u'desc 1',
  ...          'title': u'M 2', 'body': u'test m2', 'nodeType': 'menu' },
  ...         {'name': 'm21', 'path': 'm2', 'description': u'',
  ...          'title': u'M 21', 'body': u'test m21', 'nodeType': 'page' },
  ...         {'name': 'm114', 'path': 'm1/m11', 'description': u'',
  ...          'title': u'M 114', 'body': u'test m114', 'nodeType': 'page' },]
  >>> loader.load(data)
  >>> views['m2']['m21'].title
  u'M 21'
  >>> views['m1']['m11']['m114'].title
  u'M 114'

  >>> from loops.external import NodesExporter, NodesImporter
  >>> exporter = NodesExporter(views)
  >>> data = exporter.extractData()
  >>> len(data)
  8
  >>> data[3]['path']
  u'm1/m11'
  >>> data[3]['name']
  u'm112'

  >>> dumpname = os.path.dirname(__file__) + '/test.tmp'
  >>> exporter.filename = dumpname
  >>> exporter.dumpData()

Load them again from the exported file:
  
  >>> importer = NodesImporter(views)
  >>> importer.filename = dumpname
  >>> imported = importer.getData()
  >>> imported == data
  True

  >>> loader.load(imported)


Fin de partie
=============

  >>> os.unlink(dumpname)
  >>> placefulTearDown()