[Zope-Checkins] SVN: Products.Five/branches/alecm-viewlet-support/ Viewlets and content providers along with tests mostly adapted from the zope3 tests.

Alec Mitchell apm13 at columbia.edu
Mon May 1 01:02:47 EDT 2006


Log message for revision 67780:
  Viewlets and content providers along with tests mostly adapted from the zope3 tests.
  

Changed:
  U   Products.Five/branches/alecm-viewlet-support/README.txt
  A   Products.Five/branches/alecm-viewlet-support/browser/ProviderExpression.py
  U   Products.Five/branches/alecm-viewlet-support/browser/TrustedExpression.py
  U   Products.Five/branches/alecm-viewlet-support/browser/pagetemplatefile.py
  A   Products.Five/branches/alecm-viewlet-support/browser/tests/provider.txt
  A   Products.Five/branches/alecm-viewlet-support/browser/tests/provider.zcml
  A   Products.Five/branches/alecm-viewlet-support/browser/tests/provider_error.pt
  A   Products.Five/branches/alecm-viewlet-support/browser/tests/provider_messagebox.pt
  A   Products.Five/branches/alecm-viewlet-support/browser/tests/provider_namespace.pt
  A   Products.Five/branches/alecm-viewlet-support/browser/tests/provider_namespace2.pt
  A   Products.Five/branches/alecm-viewlet-support/browser/tests/test_provider.py
  A   Products.Five/branches/alecm-viewlet-support/viewlet/
  A   Products.Five/branches/alecm-viewlet-support/viewlet/README.txt
  A   Products.Five/branches/alecm-viewlet-support/viewlet/__init__.py
  A   Products.Five/branches/alecm-viewlet-support/viewlet/configure.zcml
  A   Products.Five/branches/alecm-viewlet-support/viewlet/css_viewlet.pt
  A   Products.Five/branches/alecm-viewlet-support/viewlet/directives.txt
  A   Products.Five/branches/alecm-viewlet-support/viewlet/javascript_viewlet.pt
  A   Products.Five/branches/alecm-viewlet-support/viewlet/manager.py
  A   Products.Five/branches/alecm-viewlet-support/viewlet/meta.zcml
  A   Products.Five/branches/alecm-viewlet-support/viewlet/metaconfigure.py
  A   Products.Five/branches/alecm-viewlet-support/viewlet/tests.py
  A   Products.Five/branches/alecm-viewlet-support/viewlet/viewlet.py

-=-
Modified: Products.Five/branches/alecm-viewlet-support/README.txt
===================================================================
--- Products.Five/branches/alecm-viewlet-support/README.txt	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/README.txt	2006-05-01 05:02:46 UTC (rev 67780)
@@ -36,6 +36,8 @@
 
 * Zope 2 security declarations in ZCML instead of in Python code.
 
+* Content Providers and Viewlets
+
 Together with another product, CMFonFive, Five can integrate into CMF.
 
 For more information, see ``doc/features.txt``.

Added: Products.Five/branches/alecm-viewlet-support/browser/ProviderExpression.py
===================================================================
--- Products.Five/branches/alecm-viewlet-support/browser/ProviderExpression.py	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/browser/ProviderExpression.py	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,62 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Provider tales expression registrations
+
+$Id: tales.py 39606 2005-10-25 02:59:26Z srichter $
+"""
+__docformat__ = 'restructuredtext'
+from Products.PageTemplates.Expressions import StringExpr
+from Products.PageTemplates.Expressions import getEngine
+from AccessControl.ZopeGuards import guarded_hasattr
+from AccessControl.ZopeSecurityPolicy import getRoles
+import Products.Five.security
+
+import zope.component
+import zope.schema
+import zope.interface
+from zope.contentprovider import interfaces
+from zope.contentprovider.tales import addTALNamespaceData
+
+_noroles = []
+
+class ProviderExpr(StringExpr):
+    """A provider expression for Zope2 templates.
+    """
+
+    zope.interface.implements(interfaces.ITALESProviderExpression)
+    def __call__(self, econtext):
+        name = StringExpr.__call__(self, econtext)
+        context = econtext.vars['context']
+        request = econtext.vars['request']
+        view = econtext.vars['view']
+
+        # Try to look up the provider.
+        provider = zope.component.queryMultiAdapter(
+            (context, request, view), interfaces.IContentProvider, name)
+
+        # Provide a useful error message, if the provider was not found.
+        if provider is None:
+            raise interfaces.ContentProviderLookupError(name)
+
+        # Insert the data gotten from the context
+        addTALNamespaceData(provider, econtext)
+
+        # Stage 1: Do the state update.
+        provider.update()
+
+        # Stage 2: Render the HTML content.
+        return provider.render()
+
+# Register Provider expression
+getEngine().registerType('provider', ProviderExpr)

Modified: Products.Five/branches/alecm-viewlet-support/browser/TrustedExpression.py
===================================================================
--- Products.Five/branches/alecm-viewlet-support/browser/TrustedExpression.py	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/browser/TrustedExpression.py	2006-05-01 05:02:46 UTC (rev 67780)
@@ -25,6 +25,8 @@
      getEngine, installHandlers,\
      SecureModuleImporter
 
+from ProviderExpression import ProviderExpr
+
 from ReuseUtils import rebindFunction
 
 ModuleImporter = SecureModuleImporter
@@ -52,7 +54,7 @@
 
   if isinstance(path, str): path = path.split('/')
   else: path=list(path)
-  
+
   REQUEST = get(ob, 'REQUEST', None)
   if REQUEST is None:
     REQUEST=FakeRequest()
@@ -91,7 +93,7 @@
           raise KeyError(name)
         object = o
         continue
-        
+
     t=get(object, '__bobo_traverse__', M)
     if t is not M: o=t(REQUEST, name)
     else:
@@ -123,18 +125,21 @@
   __init__ = rebindFunction(StringExpr.__init__.im_func,
                             PathExpr=PathExpr,
                             )
-  
+
+
 installHandlers = rebindFunction(installHandlers,
                                  PathExpr=PathExpr,
                                  StringExpr=StringExpr,
                                  PythonExpr=PythonExpr,
                                  )
 
+def installHandlers2(engine):
+    installHandlers(engine)
+    engine.registerType('provider', ProviderExpr)
+
 _engine=None
 getEngine = rebindFunction(getEngine,
                            _engine=_engine,
-                           installHandlers=installHandlers
+                           installHandlers=installHandlers2
                            )
 
-
-  

Modified: Products.Five/branches/alecm-viewlet-support/browser/pagetemplatefile.py
===================================================================
--- Products.Five/branches/alecm-viewlet-support/browser/pagetemplatefile.py	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/browser/pagetemplatefile.py	2006-05-01 05:02:46 UTC (rev 67780)
@@ -33,10 +33,10 @@
     Uses Zope 2's engine, but with security disabled and with some
     initialization and API from Zope 3.
     """
-        
+
     def __init__(self, filename, _prefix=None, content_type=None):
         # XXX doesn't use content_type yet
-        
+
         self.ZBindings_edit(self._default_bindings)
 
         path = self.get_path_from_prefix(_prefix)
@@ -53,8 +53,8 @@
             PageTemplateFile.__init__(self, self.filename, _prefix)
         except:
             pass
-        
- 
+
+
     def get_path_from_prefix(self, _prefix):
         if isinstance(_prefix, str):
             path = _prefix
@@ -62,24 +62,24 @@
             if _prefix is None:
                 _prefix = sys._getframe(2).f_globals
             path = package_home(_prefix)
-        return path 
+        return path
 
     _cook = rebindFunction(PageTemplateFile._cook,
                            getEngine=getEngine)
-    
+
     pt_render = rebindFunction(PageTemplateFile.pt_render,
                                getEngine=getEngine)
 
     def _pt_getContext(self):
         try:
             root = self.getPhysicalRoot()
-            view = self._getContext()
         except AttributeError:
-            # self has no attribute getPhysicalRoot. This typically happens 
-            # when the template has no proper acquisition context. 
-            # That also means it has no view.  /regebro
             root = self.context.getPhysicalRoot()
-            view = None
+        # Even if the context isn't a view (when would that be exaclty?),
+        # there shouldn't be any dange in applying a view, because it
+        # won't be used.  However assuming that a lack of getPhysicalRoot
+        # implies a missing view causes problems.
+        view = self._getContext()
 
         here = self.context.aq_inner
 
@@ -94,7 +94,7 @@
              'request': request,
              'modules': ModuleImporter,
              }
-        if view:
+        if view is not None:
             c['view'] = view
             c['views'] = ViewMapper(here, request)
 

Added: Products.Five/branches/alecm-viewlet-support/browser/tests/provider.txt
===================================================================
--- Products.Five/branches/alecm-viewlet-support/browser/tests/provider.txt	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/browser/tests/provider.txt	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,191 @@
+=================
+Content Providers
+=================
+
+We need some tests for the Zope2 versions of the TAL directives for provider.
+To this end we have copied the tests from zope.contentprovider and made them
+work with Five.  We have defined a muber of relevant views which use the
+new tal expression in providers.zcml:
+
+  >>> from zope.contentprovider import interfaces
+  >>> import Products.Five.browser.tests
+  >>> from Products.Five import zcml
+  >>> zcml.load_config("configure.zcml", Products.Five)
+  >>> zcml.load_config('provider.zcml', package=Products.Five.browser.tests)
+
+Content Providers
+-----------------
+
+Content Provider is a term from the Java world that refers to components that
+can provide HTML content. It means nothing more! How the content is found and
+returned is totally up to the implementation. The Zope 3 touch to the concept
+is that content providers are multi-adapters that are looked up by the
+context, request (and thus the layer/skin), and view they are displayed in.
+
+So let's create a simple content provider:
+
+  >>> import zope.interface
+  >>> import zope.component
+  >>> from zope.publisher.interfaces import browser
+
+  >>> class MessageBox(object):
+  ...     zope.interface.implements(interfaces.IContentProvider)
+  ...     zope.component.adapts(zope.interface.Interface,
+  ...                           browser.IDefaultBrowserLayer,
+  ...                           zope.interface.Interface)
+  ...     message = u'My Message'
+  ...
+  ...     def __init__(self, context, request, view):
+  ...         self.__parent__ = view
+  ...
+  ...     def update(self):
+  ...         pass
+  ...
+  ...     def render(self):
+  ...         return u'<div class="box">%s</div>' %self.message
+
+The ``update()`` method is executed during phase one. Since no state needs to
+be calculated and no data is modified by this simple content provider, it is
+an empty implementation. The ``render()`` method implements phase 2 of the
+process. We can now instantiate the content provider (manually) and render it:
+
+  >>> box = MessageBox(None, None, None)
+  >>> box.render()
+  u'<div class="box">My Message</div>'
+
+Since our content provider did not require the context, request or view to
+create its HTML content, we were able to pass trivial dummy values into the
+constructor. Also note that the provider must have a parent (using the
+``__parent__`` attribute) specified at all times. The parent must be the view
+the provider appears in.
+
+The TALES ``provider`` Expression
+---------------------------------
+
+The ``provider`` expression will look up the name of the content provider,
+call it and return the HTML content. The first step, however, will be to
+register our content provider with the component architecture:
+
+  >>> zope.component.provideAdapter(MessageBox, name='mypage.MessageBox')
+
+The content provider must be registered by name, since the TALES expression
+uses the name to look up the provider at run time.
+
+  >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
+  >>> manage_addSimpleContent(self.folder, 'content_obj', 'ContentObj')
+  >>> content = self.folder.content_obj
+
+Finally we publish the view:
+
+  >>> print http(r'''
+  ... GET /test_folder_1_/content_obj/main.html HTTP/1.1
+  ... ''')
+  HTTP/1.1 200 OK
+  ...
+  <html>
+    <body>
+      <h1>My Web Page</h1>
+      <div class="left-column">
+        <div class="box">My Message</div>
+      </div>
+      <div class="main">
+        Content here
+      </div>
+    </body>
+  </html>
+
+
+Failure to lookup a Content Provider
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+  >>> print http(r'''
+  ... GET /test_folder_1_/content_obj/error.html HTTP/1.1
+  ... ''')
+  HTTP/1.1 500 Internal Server Error
+  ...
+  ...ContentProviderLookupError: 'mypage.UnknownName'
+  ...
+
+Additional Data from TAL
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``provider`` expression allows also for transferring data from the TAL
+context into the content provider. This is accomplished by having the content
+provider implement an interface that specifies the attributes and provides
+``ITALNamespaceData``:
+
+  >>> import zope.schema
+  >>> class IMessageText(zope.interface.Interface):
+  ...     message = zope.schema.Text(title=u'Text of the message box')
+
+  >>> zope.interface.directlyProvides(IMessageText,
+  ...                                 interfaces.ITALNamespaceData)
+
+Now the message box can receive its text from the TAL environment:
+
+  >>> class DynamicMessageBox(MessageBox):
+  ...     zope.interface.implements(IMessageText)
+
+  >>> zope.component.provideAdapter(
+  ...     DynamicMessageBox, provides=interfaces.IContentProvider,
+  ...     name='mypage.DynamicMessageBox')
+
+Now we should get two message boxes with different text:
+
+  >>> print http(r'''
+  ... GET /test_folder_1_/content_obj/namespace.html HTTP/1.1
+  ... ''')
+  HTTP/1.1 200 OK
+  ...
+  <html>
+    <body>
+      <h1>My Web Page</h1>
+      <div class="left-column">
+        <div class="box">Hello World!</div>
+        <div class="box">Hello World again!</div>
+      </div>
+      <div class="main">
+        Content here
+      </div>
+    </body>
+  </html>
+
+Finally, a content provider can also implement several ``ITALNamespaceData``:
+
+  >>> class IMessageType(zope.interface.Interface):
+  ...     type = zope.schema.TextLine(title=u'The type of the message box')
+
+  >>> zope.interface.directlyProvides(IMessageType,
+  ...                                 interfaces.ITALNamespaceData)
+
+We'll change our message box content provider implementation a bit, so the new
+information is used:
+
+  >>> class BetterDynamicMessageBox(DynamicMessageBox):
+  ...     zope.interface.implements(IMessageType)
+  ...     type = None
+  ...
+  ...     def render(self):
+  ...         return u'<div class="box,%s">%s</div>' %(self.type, self.message)
+
+  >>> zope.component.provideAdapter(
+  ...     BetterDynamicMessageBox, provides=interfaces.IContentProvider,
+  ...     name='mypage.MessageBox')
+
+  >>> print http(r'''
+  ... GET /test_folder_1_/content_obj/namespace2.html HTTP/1.1
+  ... ''')
+  HTTP/1.1 200 OK
+  ...
+  <html>
+    <body>
+      <h1>My Web Page</h1>
+      <div class="left-column">
+        <div class="box,error">Hello World!</div>
+        <div class="box,warning">Hello World again!</div>
+      </div>
+      <div class="main">
+        Content here
+      </div>
+    </body>
+  </html>

Added: Products.Five/branches/alecm-viewlet-support/browser/tests/provider.zcml
===================================================================
--- Products.Five/branches/alecm-viewlet-support/browser/tests/provider.zcml	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/browser/tests/provider.zcml	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,33 @@
+<configure xmlns:browser="http://namespaces.zope.org/browser"
+           xmlns:meta="http://namespaces.zope.org/meta">
+
+  <!-- make the zope2.Public permission work -->
+  <meta:redefinePermission from="zope2.Public" to="zope.Public" />
+
+  <!-- stuff for content providers -->
+  <browser:page
+      for="Products.Five.tests.testing.simplecontent.ISimpleContent"
+      template="provider_messagebox.pt"
+      name="main.html"
+      permission="zope2.Public"
+      />
+  <browser:page
+      for="Products.Five.tests.testing.simplecontent.ISimpleContent"
+      template="provider_error.pt"
+      name="error.html"
+      permission="zope2.Public"
+      />
+  <browser:page
+      for="Products.Five.tests.testing.simplecontent.ISimpleContent"
+      template="provider_namespace.pt"
+      name="namespace.html"
+      permission="zope2.Public"
+      />
+  <browser:page
+      for="Products.Five.tests.testing.simplecontent.ISimpleContent"
+      template="provider_namespace2.pt"
+      name="namespace2.html"
+      permission="zope2.Public"
+      />
+
+</configure>

Added: Products.Five/branches/alecm-viewlet-support/browser/tests/provider_error.pt
===================================================================
--- Products.Five/branches/alecm-viewlet-support/browser/tests/provider_error.pt	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/browser/tests/provider_error.pt	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,5 @@
+ <html>
+   <body>
+     <tal:block replace="structure provider:mypage.UnknownName" />
+   </body>
+ </html>

Added: Products.Five/branches/alecm-viewlet-support/browser/tests/provider_messagebox.pt
===================================================================
--- Products.Five/branches/alecm-viewlet-support/browser/tests/provider_messagebox.pt	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/browser/tests/provider_messagebox.pt	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,11 @@
+ <html>
+   <body>
+     <h1>My Web Page</h1>
+     <div class="left-column">
+       <tal:block replace="structure provider:mypage.MessageBox" />
+     </div>
+     <div class="main">
+       Content here
+     </div>
+   </body>
+ </html>
\ No newline at end of file

Added: Products.Five/branches/alecm-viewlet-support/browser/tests/provider_namespace.pt
===================================================================
--- Products.Five/branches/alecm-viewlet-support/browser/tests/provider_namespace.pt	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/browser/tests/provider_namespace.pt	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,14 @@
+ <html>
+   <body>
+     <h1>My Web Page</h1>
+     <div class="left-column">
+       <tal:block define="message string:Hello World!"
+                  replace="structure provider:mypage.DynamicMessageBox" />
+       <tal:block define="message string:Hello World again!"
+                  replace="structure provider:mypage.DynamicMessageBox" />
+     </div>
+     <div class="main">
+       Content here
+     </div>
+   </body>
+ </html>

Added: Products.Five/branches/alecm-viewlet-support/browser/tests/provider_namespace2.pt
===================================================================
--- Products.Five/branches/alecm-viewlet-support/browser/tests/provider_namespace2.pt	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/browser/tests/provider_namespace2.pt	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,16 @@
+ <html>
+   <body>
+     <h1>My Web Page</h1>
+     <div class="left-column">
+       <tal:block define="message string:Hello World!;
+                          type string:error"
+                  replace="structure provider:mypage.MessageBox" />
+       <tal:block define="message string:Hello World again!;
+                          type string:warning"
+                  replace="structure provider:mypage.MessageBox" />
+     </div>
+     <div class="main">
+       Content here
+     </div>
+   </body>
+ </html>

Added: Products.Five/branches/alecm-viewlet-support/browser/tests/test_provider.py
===================================================================
--- Products.Five/branches/alecm-viewlet-support/browser/tests/test_provider.py	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/browser/tests/test_provider.py	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test browser pages
+
+$Id: test_skin.py 61865 2005-11-19 09:54:53Z philikon $
+"""
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+def test_suite():
+    from Testing.ZopeTestCase import FunctionalDocFileSuite
+    return FunctionalDocFileSuite('provider.txt',
+                                  package='Products.Five.browser.tests')
+
+if __name__ == '__main__':
+    framework()

Added: Products.Five/branches/alecm-viewlet-support/viewlet/README.txt
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/README.txt	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/README.txt	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,939 @@
+=============================
+Viewlets and Viewlet Managers
+=============================
+
+Let's start with some motivation. Using content providers allows us to insert
+one piece of HTML content. In most Web development, however, you are often
+interested in defining some sort of region and then allow developers to
+register content for those regions.
+
+  >>> from zope.viewlet import interfaces
+
+Setup traversal stuff
+
+  >>> import Products.Five
+  >>> from Products.Five import zcml
+  >>> zcml.load_config("configure.zcml", Products.Five)
+
+Set a loose security policy because these are unit tests, security will be
+tested in another file:
+
+  >>> from AccessControl import SecurityManager
+  >>> from Products.Five.viewlet.tests import UnitTestSecurityPolicy
+  >>> from AccessControl.SecurityManagement import newSecurityManager
+  >>> from AccessControl.SecurityManagement import noSecurityManager
+  >>> noSecurityManager()
+  >>> oldPolicy = SecurityManager.setSecurityPolicy(UnitTestSecurityPolicy())
+
+Design Notes
+------------
+
+As mentioned above, besides inserting snippets of HTML at places, we more
+frequently want to define a region in our page and allow specialized content
+providers to be inserted based on configuration. Those specialized content
+providers are known viewlets and are only available inside viewlet managers,
+which are just a more complex example of content providers.
+
+Unfortunately, the Java world does not implement this layer separately. The
+viewlet manager is most similar to a Java "channel", but we decided against
+using this name, since it is very generic and not very meaningful. The viewlet
+has no Java counterpart, since Java does not implement content providers using
+a component architecture and thus does not register content providers
+specifically for viewlet managers, which I believe makes the Java
+implementation less usefull as a generic concept. In fact, the main design
+goal in the Java world is the implementation of reusable and sharable
+portlets. The scope for Zope 3 is larger, since we want to provide a generic
+framework for building pluggable user interfaces.
+
+
+The Viewlet Manager
+-------------------
+
+In this implementation of viewlets, those regions are just content providers
+called viewlet managers that manage a special type of content providers known
+as viewlets. Every viewlet manager handles the viewlets registered for it:
+
+  >>> from Products.Five.viewlet.tests import ILeftColumn
+
+You can then create a viewlet manager using this interface now:
+
+  >>> from Products.Five.viewlet import manager
+  >>> LeftColumn = manager.ViewletManager('left', ILeftColumn)
+
+Now we have to instantiate it in the context of an actual zope object:
+
+  >>> import zope.interface
+  >>> from OFS import SimpleItem, Folder
+  >>> class Content(SimpleItem.SimpleItem):
+  ...     zope.interface.implements(zope.interface.Interface)
+  >>> obj_id = self.folder._setObject('content1', Content())
+  >>> content = self.folder[obj_id]
+
+  >>> from Products.Five.traversable import FakeRequest
+  >>> request = FakeRequest()
+  >>> from zope.app.publication.browser import setDefaultSkin
+  >>> setDefaultSkin(request)
+
+  >>> from Products.Five.browser import BrowserView as View
+  >>> view = View(content, request)
+
+  >>> leftColumn = LeftColumn(content, request, view)
+
+So initially nothing gets rendered:
+
+  >>> leftColumn.update()
+  >>> leftColumn.render()
+  u''
+
+But now we register some viewlets for the manager
+
+  >>> import zope.component
+  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+  >>> from zope.app.publisher.interfaces.browser import IBrowserView
+
+  >>> from Acquisition import Explicit
+  >>> class WeatherBox(Explicit):
+  ...     zope.interface.implements(interfaces.IViewlet)
+  ...
+  ...     def __init__(self, context, request, view, manager):
+  ...         self.__parent__ = view
+  ...         self.context = context
+  ...
+  ...     def update(self):
+  ...         pass
+  ...
+  ...     def render(self):
+  ...         return u'<div class="box">It is sunny today!</div>'
+
+  >>> zope.component.provideAdapter(
+  ...     WeatherBox,
+  ...     (zope.interface.Interface, IDefaultBrowserLayer,
+  ...     IBrowserView, ILeftColumn),
+  ...     interfaces.IViewlet, name='weather')
+
+  >>> class SportBox(Explicit):
+  ...     zope.interface.implements(interfaces.IViewlet)
+  ...
+  ...     def __init__(self, context, request, view, manager):
+  ...         self.__parent__ = view
+  ...         self.context = context
+  ...
+  ...     def update(self):
+  ...         pass
+  ...
+  ...     def render(self):
+  ...         return u'<div class="box">Patriots (23) : Steelers (7)</div>'
+
+  >>> zope.component.provideAdapter(
+  ...     SportBox,
+  ...     (zope.interface.Interface, IDefaultBrowserLayer,
+  ...      IBrowserView, ILeftColumn),
+  ...     interfaces.IViewlet, name='sport')
+
+and thus the left column is filled:
+
+  >>> leftColumn.update()
+  >>> print leftColumn.render()
+  <div class="box">Patriots (23) : Steelers (7)</div>
+  <div class="box">It is sunny today!</div>
+
+But this is of course pretty lame, since there is no way of specifying how the
+viewlets are put together. But we have a solution. The second argument of the
+``ViewletManager()`` function is a template in which we can specify how the
+viewlets are put together:
+
+  >>> import os, tempfile
+  >>> temp_dir = tempfile.mkdtemp()
+  >>> leftColTemplate = os.path.join(temp_dir, 'leftCol.pt')
+  >>> open(leftColTemplate, 'w').write('''
+  ... <div class="left-column">
+  ...   <tal:block repeat="viewlet options/viewlets"
+  ...              replace="structure viewlet/render" />
+  ... </div>
+  ... ''')
+
+  >>> LeftColumn = manager.ViewletManager('left', ILeftColumn,
+  ...                                     template=leftColTemplate)
+  >>> leftColumn = LeftColumn(content, request, view)
+
+TODO: Fix this silly thing; viewlets should be directly available.
+
+As you can see, the viewlet manager provides a global ``options/viewlets``
+variable that is an iterable of all the avialable viewlets in the correct
+order:
+
+  >>> leftColumn.update()
+  >>> print leftColumn.render().strip()
+  <div class="left-column">
+    <div class="box">Patriots (23) : Steelers (7)</div>
+    <div class="box">It is sunny today!</div>
+  </div>
+
+You can also lookup the viewlets directly for management purposes:
+
+  >>> leftColumn['weather']
+  <WeatherBox ...>
+  >>> leftColumn.get('weather')
+  <WeatherBox ...>
+
+If the viewlet is not found, then the expected behavior is provided:
+
+  >>> leftColumn['stock']
+  Traceback (most recent call last):
+  ...
+  ComponentLookupError: 'No provider with name `stock` found.'
+
+  >>> leftColumn.get('stock') is None
+  True
+
+Customizing the default Viewlet Manager
+---------------------------------------
+
+One important feature of any viewlet manager is to be able to filter and sort
+the viewlets it is displaying. The default viewlet manager that we have been
+using in the tests above, supports filtering by access availability and
+sorting via the viewlet's ``__cmp__()`` method (default). You can easily
+override this default policy by providing a base viewlet manager class.
+
+In our case we will manage the viewlets using a global list:
+
+  >>> shown = ['weather', 'sport']
+
+The viewlet manager base class now uses this list:
+
+  >>> class ListViewletManager(object):
+  ...
+  ...     def filter(self, viewlets):
+  ...         viewlets = super(ListViewletManager, self).filter(viewlets)
+  ...         return [(name, viewlet)
+  ...                 for name, viewlet in viewlets
+  ...                 if name in shown]
+  ...
+  ...     def sort(self, viewlets):
+  ...         viewlets = dict(viewlets)
+  ...         return [(name, viewlets[name]) for name in shown]
+
+Let's now create a new viewlet manager:
+
+  >>> LeftColumn = manager.ViewletManager(
+  ...     'left', ILeftColumn, bases=(ListViewletManager,),
+  ...     template=leftColTemplate)
+  >>> leftColumn = LeftColumn(content, request, view)
+
+So we get the weather box first and the sport box second:
+
+  >>> leftColumn.update()
+  >>> print leftColumn.render().strip()
+  <div class="left-column">
+    <div class="box">It is sunny today!</div>
+    <div class="box">Patriots (23) : Steelers (7)</div>
+  </div>
+
+Now let's change the order...
+
+  >>> shown.reverse()
+
+and the order should switch as well:
+
+  >>> leftColumn.update()
+  >>> print leftColumn.render().strip()
+  <div class="left-column">
+    <div class="box">Patriots (23) : Steelers (7)</div>
+    <div class="box">It is sunny today!</div>
+  </div>
+
+Of course, we also can remove a shown viewlet:
+
+  >>> weather = shown.pop()
+  >>> leftColumn.update()
+  >>> print leftColumn.render().strip()
+  <div class="left-column">
+    <div class="box">Patriots (23) : Steelers (7)</div>
+  </div>
+
+
+Viewlet Base Classes
+--------------------
+
+To make the creation of viewlets simpler, a set of useful base classes and
+helper functions are provided:
+
+  >>> from Products.Five.viewlet import viewlet
+
+The first class is a base class that simply defines the constructor:
+
+  >>> base = viewlet.ViewletBase('context', 'request', 'view', 'manager')
+  >>> base.context
+  'context'
+  >>> base.request
+  'request'
+  >>> base.__parent__
+  'view'
+  >>> base.manager
+  'manager'
+
+But a default ``render()`` method implementation is not provided:
+
+  >>> base.render()
+  Traceback (most recent call last):
+  ...
+  NotImplementedError: `render` method must be implemented by subclass.
+
+If you have already an existing class that produces the HTML content in some
+method, then the ``SimpleAttributeViewlet`` might be for you, since it can be
+used to convert any class quickly into a viewlet:
+
+  >>> class FooViewlet(viewlet.SimpleAttributeViewlet):
+  ...     __page_attribute__ = 'foo'
+  ...
+  ...     def foo(self):
+  ...         return 'output'
+
+The `__page_attribute__` attribute provides the name of the function to call for
+rendering.
+
+  >>> foo = FooViewlet('context', 'request', 'view', 'manager')
+  >>> foo.foo()
+  'output'
+  >>> foo.render()
+  'output'
+
+If you specify `render` as the attribute an error is raised to prevent
+infinite recursion:
+
+  >>> foo.__page_attribute__ = 'render'
+  >>> foo.render()
+  Traceback (most recent call last):
+  ...
+  AttributeError: render
+
+The same is true if the specified attribute does not exist:
+
+  >>> foo.__page_attribute__ = 'bar'
+  >>> foo.render()
+  Traceback (most recent call last):
+  ...
+  AttributeError: bar
+
+To create simple template-based viewlets you can use the
+``SimpleViewletClass()`` function. This function is very similar to its view
+equivalent and is used by the ZCML directives to create viewlets. The result
+of this function call will be a fully functional viewlet class. Let's start by
+simply specifying a template only:
+
+  >>> template = os.path.join(temp_dir, 'demoTemplate.pt')
+  >>> open(template, 'w').write('''<div>contents</div>''')
+
+  >>> Demo = viewlet.SimpleViewletClass(template)
+  >>> print Demo(content, request, view, manager).render()
+  <div>contents</div>
+
+Now let's additionally specify a class that can provide additional features:
+
+  >>> class MyViewlet(object):
+  ...     myAttribute = 8
+
+  >>> Demo = viewlet.SimpleViewletClass(template, bases=(MyViewlet,))
+  >>> MyViewlet in Demo.__bases__
+  True
+  >>> Demo(content, request, view, manager).myAttribute
+  8
+
+The final important feature is the ability to pass in further attributes to
+the class:
+
+  >>> Demo = viewlet.SimpleViewletClass(
+  ...     template, attributes={'here': 'now', 'lucky': 3})
+  >>> demo = Demo(content, request, view, manager)
+  >>> demo.here
+  'now'
+  >>> demo.lucky
+  3
+
+As for all views, they must provide a name that can also be passed to the
+function:
+
+  >>> Demo = viewlet.SimpleViewletClass(template, name='demoViewlet')
+  >>> demo = Demo(content, request, view, manager)
+  >>> demo.__name__
+  'demoViewlet'
+
+In addition to the the generic viewlet code above, the package comes with two
+viewlet base classes and helper functions for inserting CSS and Javascript
+links into HTML headers, since those two are so very common. I am only going
+to demonstrate the helper functions here, since those demonstrations will
+fully demonstrate the functionality of the base classes as well.
+
+To make resource lookup work we need to make the content traversable:
+  >>> try:
+  ...     from Products.Five.fiveconfigure import classTraversable
+  ...     classTraversable(Content)
+  ...
+  ... except ImportError:
+  ...     pass
+
+The viewlet will look up the resource it was given and tries to produce the
+absolute URL for it:
+
+  >>> class JSResource(Explicit):
+  ...     def __init__(self, request):
+  ...         self.request = request
+  ...
+  ...     def __call__(self):
+  ...         return '/@@/resource.js'
+
+  >>> from zope.app.testing import ztapi
+  >>> ztapi.browserResource('resource.js', JSResource)
+  >>> JSViewlet = viewlet.JavaScriptViewlet('resource.js')
+  >>> print JSViewlet(content, request, view, manager).render().strip()
+  <script type="text/javascript" src="/@@/resource.js">
+  </script>
+
+The same works for the CSS resource viewlet:
+
+  >>> class CSSResource(Explicit):
+  ...     def __init__(self, request):
+  ...         self.request = request
+  ...
+  ...     def __call__(self):
+  ...         return '/@@/resource.css'
+
+  >>> ztapi.browserResource('resource.css', CSSResource)
+
+  >>> CSSViewlet = viewlet.CSSViewlet('resource.css')
+  >>> print CSSViewlet(content, request, view, manager).render().strip()
+  <link type="text/css" rel="stylesheet"
+        href="/@@/resource.css" media="all" />
+
+You can also change the media type and the rel attribute:
+
+  >>> CSSViewlet = viewlet.CSSViewlet('resource.css', media='print', rel='css')
+  >>> print CSSViewlet(content, request, view, manager).render().strip()
+  <link type="text/css" rel="css" href="/@@/resource.css"
+        media="print" />
+
+
+A Complex Example
+-----------------
+
+The Data
+~~~~~~~~
+
+So far we have only demonstrated simple (maybe overly trivial) use cases of
+the viewlet system. In the following example, we are going to develop a
+generic contents view for files. The step is to create a file component:
+
+  >>> class IFile(zope.interface.Interface):
+  ...     data = zope.interface.Attribute('Data of file.')
+
+  >>> class File(SimpleItem.SimpleItem):
+  ...     zope.interface.implements(IFile)
+  ...     def __init__(self, data=''):
+  ...         self.__name__ = ''
+  ...         self.data = data
+
+Since we want to also provide the size of a file, here a simple implementation
+of the ``ISized`` interface:
+
+  >>> from zope.app import size
+  >>> class FileSized(object):
+  ...     zope.interface.implements(size.interfaces.ISized)
+  ...     zope.component.adapts(IFile)
+  ...
+  ...     def __init__(self, file):
+  ...         self.file = file
+  ...
+  ...     def sizeForSorting(self):
+  ...         return 'byte', len(self.file.data)
+  ...
+  ...     def sizeForDisplay(self):
+  ...         return '%i bytes' %len(self.file.data)
+
+  >>> zope.component.provideAdapter(FileSized)
+
+We also need a container to which we can add files:
+
+  >>> class Container(Folder.Folder):
+  ...     def __setitem__(self, name, value):
+  ...         self._setObject(name, value)
+  ...         value.__name__ = name
+
+Here is some sample data:
+
+  >>> container = Container()
+  >>> obj_id = self.folder._setObject('container', container)
+  >>> container = self.folder[obj_id]
+  >>> container['mypage.html'] = File('<html><body>Hello World!</body></html>')
+  >>> container['data.xml'] = File('<message>Hello World!</message>')
+  >>> container['test.txt'] = File('Hello World!')
+
+
+The View
+~~~~~~~~
+
+The contents view of the container should iterate through the container and
+represent the files in a table:
+
+  >>> contentsTemplate = os.path.join(temp_dir, 'contents.pt')
+  >>> open(contentsTemplate, 'w').write('''
+  ... <html>
+  ...   <body>
+  ...     <h1>Cotnents</h1>
+  ...     <div tal:content="structure provider:contents" />
+  ...   </body>
+  ... </html>
+  ... ''')
+
+  >>> from Products.Five.browser.metaconfigure import makeClassForTemplate
+  >>> Contents = makeClassForTemplate(contentsTemplate, name='contents.html')
+
+
+The Viewlet Manager
+~~~~~~~~~~~~~~~~~~~
+
+Now we have to write our own viewlet manager. In this case we cannot use the
+default implementation, since the viewlets will be looked up for each
+different item:
+
+  >>> shownColumns = []
+
+  >>> class ContentsViewletManager(Explicit):
+  ...     zope.interface.implements(interfaces.IViewletManager)
+  ...     index = None
+  ...
+  ...     def __init__(self, context, request, view):
+  ...         self.context = context
+  ...         self.request = request
+  ...         self.__parent__ = view
+  ...
+  ...     def update(self):
+  ...         rows = []
+  ...         for name, value in self.context.objectItems():
+  ...             rows.append(
+  ...                 [zope.component.getMultiAdapter(
+  ...                     (value, self.request, self.__parent__, self),
+  ...                     interfaces.IViewlet, name=colname)
+  ...                  for colname in shownColumns])
+  ...             [entry.update() for entry in rows[-1]]
+  ...         self.rows = rows
+  ...
+  ...     def render(self, *args, **kw):
+  ...         return self.index(*args, **kw)
+
+Now we need a template to produce the contents table:
+
+  >>> tableTemplate = os.path.join(temp_dir, 'table.pt')
+  >>> open(tableTemplate, 'w').write('''
+  ... <table>
+  ...   <tr tal:repeat="row view/rows">
+  ...     <td tal:repeat="column row">
+  ...       <tal:block replace="structure column/render" />
+  ...     </td>
+  ...   </tr>
+  ... </table>
+  ... ''')
+
+From the two pieces above, we can generate the final viewlet manager class and
+register it (it's a bit tedious, I know):
+
+  >>> from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
+  >>> ContentsViewletManager = type(
+  ...     'ContentsViewletManager', (ContentsViewletManager,),
+  ...     {'index': ZopeTwoPageTemplateFile('table.pt', temp_dir)})
+
+  >>> zope.component.provideAdapter(
+  ...     ContentsViewletManager,
+  ...     (Container, IDefaultBrowserLayer, zope.interface.Interface),
+  ...     interfaces.IViewletManager, name='contents')
+
+Since we have not defined any viewlets yet, the table is totally empty:
+
+  >>> contents = Contents(container, request)
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+          </tr>
+          <tr>
+          </tr>
+          <tr>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+
+The Viewlets and the Final Result
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Now let's create a first viewlet for the manager...
+
+  >>> class NameViewlet(Explicit):
+  ...
+  ...     def __init__(self, context, request, view, manager):
+  ...         self.__parent__ = view
+  ...         self.context = context
+  ...
+  ...     def update(self):
+  ...         pass
+  ...
+  ...     def render(self):
+  ...         return self.context.__name__
+
+and register it:
+
+  >>> zope.component.provideAdapter(
+  ...     NameViewlet,
+  ...     (IFile, IDefaultBrowserLayer,
+  ...      zope.interface.Interface, interfaces.IViewletManager),
+  ...     interfaces.IViewlet, name='name')
+
+Note how you register the viewlet on ``IFile`` and not on the container. Now
+we should be able to see the name for each file in the container:
+
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+          </tr>
+          <tr>
+          </tr>
+          <tr>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+Waaa, nothing there! What happened? Well, we have to tell our user preferences
+that we want to see the name as a column in the table:
+
+  >>> shownColumns = ['name']
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+            <td>
+              mypage.html
+            </td>
+          </tr>
+          <tr>
+            <td>
+              data.xml
+            </td>
+          </tr>
+          <tr>
+            <td>
+              test.txt
+            </td>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+Let's now write a second viewlet that will display the size of the object for
+us:
+
+  >>> class SizeViewlet(Explicit):
+  ...
+  ...     def __init__(self, context, request, view, manager):
+  ...         self.__parent__ = view
+  ...         self.context = context
+  ...
+  ...     def update(self):
+  ...         pass
+  ...
+  ...     def render(self):
+  ...         return size.interfaces.ISized(self.context).sizeForDisplay()
+
+  >>> zope.component.provideAdapter(
+  ...     SizeViewlet,
+  ...     (IFile, IDefaultBrowserLayer,
+  ...      zope.interface.Interface, interfaces.IViewletManager),
+  ...     interfaces.IViewlet, name='size')
+
+After we added it to the list of shown columns,
+
+  >>> shownColumns = ['name', 'size']
+
+we can see an entry for it:
+
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+            <td>
+              mypage.html
+            </td>
+            <td>
+              38 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              data.xml
+            </td>
+            <td>
+              31 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              test.txt
+            </td>
+            <td>
+              12 bytes
+            </td>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+If we switch the two columns around,
+
+  >>> shownColumns = ['size', 'name']
+
+the result will be
+
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+            <td>
+              38 bytes
+            </td>
+            <td>
+              mypage.html
+            </td>
+          </tr>
+          <tr>
+            <td>
+              31 bytes
+            </td>
+            <td>
+              data.xml
+            </td>
+          </tr>
+          <tr>
+            <td>
+              12 bytes
+            </td>
+            <td>
+              test.txt
+            </td>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+
+Supporting Sorting
+~~~~~~~~~~~~~~~~~~
+
+Oftentimes you also want to batch and sort the entries in a table. Since those
+two features are not part of the view logic, they should be treated with
+independent components. In this example, we are going to only implement
+sorting using a simple utility:
+
+  >>> class ISorter(zope.interface.Interface):
+  ...
+  ...     def sort(values):
+  ...         """Sort the values."""
+
+  >>> class SortByName(object):
+  ...     zope.interface.implements(ISorter)
+  ...
+  ...     def sort(self, values):
+  ...         return sorted(values, lambda x, y: cmp(x.__name__, y.__name__))
+
+  >>> zope.component.provideUtility(SortByName(), name='name')
+
+  >>> class SortBySize(object):
+  ...     zope.interface.implements(ISorter)
+  ...
+  ...     def sort(self, values):
+  ...         return sorted(
+  ...             values,
+  ...             lambda x, y: cmp(size.interfaces.ISized(x).sizeForSorting(),
+  ...                              size.interfaces.ISized(y).sizeForSorting()))
+
+  >>> zope.component.provideUtility(SortBySize(), name='size')
+
+Note that we decided to give the sorter utilities the same name as the
+corresponding viewlet. This convention will make our implementation of the
+viewlet manager much simpler:
+
+  >>> sortByColumn = ''
+
+  >>> class SortedContentsViewletManager(manager.ViewletManagerBase):
+  ...     zope.interface.implements(interfaces.IViewletManager)
+  ...     index = None
+  ...
+  ...     def __init__(self, context, request, view):
+  ...         self.context = context
+  ...         self.request = request
+  ...         self.__parent__ = view
+  ...
+  ...     def update(self):
+  ...         values = self.context.objectValues()
+  ...
+  ...         if sortByColumn:
+  ...            sorter = zope.component.queryUtility(ISorter, sortByColumn)
+  ...            if sorter:
+  ...                values = sorter.sort(values)
+  ...
+  ...         rows = []
+  ...         for value in values:
+  ...             rows.append(
+  ...                 [zope.component.getMultiAdapter(
+  ...                     (value, self.request, self.__parent__, self),
+  ...                     interfaces.IViewlet, name=colname)
+  ...                  for colname in shownColumns])
+  ...             [entry.update() for entry in rows[-1]]
+  ...         self.rows = rows
+  ...
+  ...     def render(self, *args, **kw):
+  ...         return self.index(*args, **kw)
+
+As you can see, the concern of sorting is cleanly separated from generating
+the view code. In MVC terms that means that the controller (sort) is logically
+separated from the view (viewlets). Let's now do the registration dance for
+the new viewlet manager. We simply override the existing registration:
+
+  >>> SortedContentsViewletManager = type(
+  ...     'SortedContentsViewletManager', (SortedContentsViewletManager,),
+  ...     {'index': ZopeTwoPageTemplateFile('table.pt', temp_dir)})
+
+  >>> zope.component.provideAdapter(
+  ...     SortedContentsViewletManager,
+  ...     (Container, IDefaultBrowserLayer, zope.interface.Interface),
+  ...     interfaces.IViewletManager, name='contents')
+
+Finally we sort the contents by name:
+
+  >>> shownColumns = ['name', 'size']
+  >>> sortByColumn = 'name'
+
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+            <td>
+              data.xml
+            </td>
+            <td>
+              31 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              mypage.html
+            </td>
+            <td>
+              38 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              test.txt
+            </td>
+            <td>
+              12 bytes
+            </td>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+Now let's sort by size:
+
+  >>> sortByColumn = 'size'
+
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+            <td>
+              test.txt
+            </td>
+            <td>
+              12 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              data.xml
+            </td>
+            <td>
+              31 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              mypage.html
+            </td>
+            <td>
+              38 bytes
+            </td>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+That's it! As you can see, in a few steps we have built a pretty flexible
+contents view with selectable columns and sorting. However, there is a lot of
+room for extending this example:
+
+- Table Header: The table header cell for each column should be a different
+  type of viewlet, but registered under the same name. The column header
+  viewlet also adapts the container not the item. The header column should
+  also be able to control the sorting.
+
+- Batching: A simple implementation of batching should work very similar to
+  the sorting feature. Of course, efficient implementations should somehow
+  combine batching and sorting more effectively.
+
+- Sorting in ascending and descending order: Currently, you can only sort from
+  the smallest to the highest value; however, this limitation is almost
+  superficial and can easily be removed by making the sorters a bit more
+  flexible.
+
+- Further Columns: For a real application, you would want to implement other
+  columns, of course. You would also probably want some sort of fallback for
+  the case that a viewlet is not found for a particular container item and
+  column.
+
+
+Cleanup
+-------
+
+  >>> ignore = SecurityManager.setSecurityPolicy(oldPolicy)
+  >>> import shutil
+  >>> shutil.rmtree(temp_dir)

Added: Products.Five/branches/alecm-viewlet-support/viewlet/__init__.py
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/__init__.py	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/__init__.py	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1 @@
+# A package for viewlet support
\ No newline at end of file

Added: Products.Five/branches/alecm-viewlet-support/viewlet/configure.zcml
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/configure.zcml	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/configure.zcml	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,12 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:browser="http://namespaces.zope.org/browser">
+
+  <interface
+      interface="zope.viewlet.interfaces.IViewletManager"
+      />
+
+  <interface
+      interface="zope.viewlet.interfaces.IViewletManager"
+      />
+
+</configure>
\ No newline at end of file

Added: Products.Five/branches/alecm-viewlet-support/viewlet/css_viewlet.pt
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/css_viewlet.pt	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/css_viewlet.pt	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,4 @@
+<link type="text/css" rel="stylesheet" href="somestyle.css" media="all"
+      tal:attributes="rel view/getRel;
+                      href view/getURL;
+                      media view/getMedia" />

Added: Products.Five/branches/alecm-viewlet-support/viewlet/directives.txt
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/directives.txt	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/directives.txt	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,502 @@
+================================
+The ``viewletManager`` Directive
+================================
+
+Setup traversal stuff
+
+  >>> import Products.Five
+  >>> from Products.Five import zcml
+  >>> zcml.load_config("configure.zcml", Products.Five)
+
+The ``viewletManager`` directive allows you to quickly register a new viewlet
+manager without worrying about the details of the ``adapter``
+directive. Before we can use the directives, we have to register their
+handlers by executing the package's meta configuration:
+
+  >>> from Products.Five import zcml
+  >>> context = zcml.load_string('''
+  ... <configure i18n_domain="zope">
+  ...   <include package="Products.Five.viewlet" file="meta.zcml" />
+  ... </configure>
+  ... ''')
+
+Now we can register a viewlet manager:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewletManager
+  ...       name="defaultmanager"
+  ...       permission="zope.Public"
+  ...       />
+  ... </configure>
+  ... ''')
+
+Let's make sure the directive has really issued a sensible adapter
+registration; to do that, we create some dummy content, request and view
+objects:
+
+  >>> from Products.Five.viewlet.tests import Content
+  >>> content = Content()
+  >>> obj_id = self.folder._setObject('content1', Content())
+  >>> content = self.folder[obj_id]
+
+  >>> from Products.Five.traversable import FakeRequest
+  >>> request = FakeRequest()
+  >>> from zope.app.publication.browser import setDefaultSkin
+  >>> setDefaultSkin(request)
+
+  >>> from Products.Five.browser import BrowserView as View
+  >>> view = View(content, request)
+
+Now let's lookup the manager. This particular registration is pretty boring:
+
+  >>> import zope.component
+  >>> from zope.viewlet import interfaces
+  >>> manager = zope.component.getMultiAdapter(
+  ...     (content, request, view),
+  ...     interfaces.IViewletManager, name='defaultmanager')
+
+  >>> manager
+  <Products.Five.viewlet.manager.<ViewletManager providing IViewletManager> object ...>
+  >>> interfaces.IViewletManager.providedBy(manager)
+  True
+  >>> manager.template is None
+  True
+  >>> manager.update()
+  >>> manager.render()
+  u''
+
+However, this registration is not very useful, since we did specify a specific
+viewlet manager interface, a specific content interface, specific view or
+specific layer. This means that all viewlets registered will be found.
+
+The first step to effectively using the viewlet manager directive is to define
+a special viewlet manager interface:
+
+  >>> from Products.Five.viewlet.tests import ILeftColumn
+
+Now we can register register a manager providing this interface:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewletManager
+  ...       name="leftcolumn"
+  ...       permission="zope.Public"
+  ...       provides="Products.Five.viewlet.tests.ILeftColumn"
+  ...       />
+  ... </configure>
+  ... ''')
+
+  >>> manager = zope.component.getMultiAdapter(
+  ...     (content, request, view), ILeftColumn, name='leftcolumn')
+
+  >>> manager
+  <Products.Five.viewlet.manager.<ViewletManager providing ILeftColumn> object ...>
+  >>> ILeftColumn.providedBy(manager)
+  True
+  >>> manager.template is None
+  True
+  >>> manager.update()
+  >>> manager.render()
+  u''
+
+Next let's see what happens, if we specify a template for the viewlet manager:
+
+  >>> import os, tempfile
+  >>> temp_dir = tempfile.mkdtemp()
+
+  >>> leftColumnTemplate = os.path.join(temp_dir, 'leftcolumn.pt')
+  >>> open(leftColumnTemplate, 'w').write('''
+  ... <div class="column">
+  ...    <div class="entry"
+  ...         tal:repeat="viewlet options/viewlets"
+  ...         tal:content="structure viewlet" />
+  ... </div>
+  ... ''')
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewletManager
+  ...       name="leftcolumn"
+  ...       permission="zope.Public"
+  ...       provides="Products.Five.viewlet.tests.ILeftColumn"
+  ...       template="%s"
+  ...       />
+  ... </configure>
+  ... ''' %leftColumnTemplate)
+
+  >>> manager = zope.component.getMultiAdapter(
+  ...     (content, request, view), ILeftColumn, name='leftcolumn')
+
+  >>> manager
+  <Products.Five.viewlet.manager.<ViewletManager providing ILeftColumn> object ...>
+  >>> ILeftColumn.providedBy(manager)
+  True
+  >>> manager.template.meta_type
+  'Page Template (File)'
+  >>> manager.update()
+  >>> print manager.render().strip()
+  <div class="column">
+  </div>
+
+Additionally you can specify a class that will serve as a base to the default
+viewlet manager or be a viewlet manager in its own right. In our case we will
+provide a custom implementation of the ``sort()`` method, which will sort by a
+weight attribute in the viewlet:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewletManager
+  ...       name="leftcolumn"
+  ...       permission="zope.Public"
+  ...       provides="Products.Five.viewlet.tests.ILeftColumn"
+  ...       template="%s"
+  ...       class="Products.Five.viewlet.tests.WeightBasedSorting"
+  ...       />
+  ... </configure>
+  ... ''' %leftColumnTemplate)
+
+  >>> manager = zope.component.getMultiAdapter(
+  ...     (content, request, view), ILeftColumn, name='leftcolumn')
+
+  >>> manager
+  <Products.Five.viewlet.manager.<ViewletManager providing ILeftColumn> object ...>
+  >>> manager.__class__.__bases__
+  (<class 'Products.Five.viewlet.tests.WeightBasedSorting'>,
+   <class 'Products.Five.viewlet.manager.ViewletManagerBase'>)
+  >>> ILeftColumn.providedBy(manager)
+  True
+  >>> manager.template.meta_type
+  'Page Template (File)'
+  >>> manager.update()
+  >>> print manager.render().strip()
+  <div class="column">
+  </div>
+
+Finally, if a non-existent template is specified, an error is raised:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewletManager
+  ...       name="leftcolumn"
+  ...       permission="zope.Public"
+  ...       template="foo.pt"
+  ...       />
+  ... </configure>
+  ... ''')
+  Traceback (most recent call last):
+  ...
+  ZopeXMLConfigurationError: File "<string>", line 3.2-7.8
+      ConfigurationError: ('No such file', '...foo.pt')
+
+
+=========================
+The ``viewlet`` Directive
+=========================
+
+Now that we have a viewlet manager, we have to register some viewlets for
+it. The ``viewlet`` directive is similar to the ``viewletManager`` directive,
+except that the viewlet is also registered for a particular manager interface,
+as seen below:
+
+  >>> weatherTemplate = os.path.join(temp_dir, 'weather.pt')
+  >>> open(weatherTemplate, 'w').write('''
+  ... <div>sunny</div>
+  ... ''')
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="weather"
+  ...       manager="Products.Five.viewlet.tests.ILeftColumn"
+  ...       template="%s"
+  ...       permission="zope.Public"
+  ...       extra_string_attributes="can be specified"
+  ...       />
+  ... </configure>
+  ... ''' % weatherTemplate)
+
+If we look into the adapter registry, we will find the viewlet:
+
+  >>> viewlet = zope.component.getMultiAdapter(
+  ...     (content, request, view, manager), interfaces.IViewlet,
+  ...     name='weather')
+  >>> viewlet.render().strip()
+  '<div>sunny</div>'
+  >>> viewlet.extra_string_attributes
+  u'can be specified'
+
+The manager now also gives us the output of the one and only viewlet:
+
+  >>> manager.update()
+  >>> print manager.render().strip()
+  <div class="column">
+    <div class="entry">
+      <div>sunny</div>
+    </div>
+  </div>
+
+Let's now ensure that we can also specify a viewlet class:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="weather2"
+  ...       for="*"
+  ...       manager="Products.Five.viewlet.tests.ILeftColumn"
+  ...       template="%s"
+  ...       class="Products.Five.viewlet.tests.Weather"
+  ...       permission="zope.Public"
+  ...       />
+  ... </configure>
+  ... ''' % weatherTemplate)
+
+  >>> viewlet = zope.component.getMultiAdapter(
+  ...     (content, request, view, manager), interfaces.IViewlet,
+  ...     name='weather2')
+  >>> viewlet().strip()
+  '<div>sunny</div>'
+
+Okay, so the template-driven cases work. But just specifying a class should
+also work:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="sport"
+  ...       for="*"
+  ...       manager="Products.Five.viewlet.tests.ILeftColumn"
+  ...       class="Products.Five.viewlet.tests.Sport"
+  ...       permission="zope.Public"
+  ...       />
+  ... </configure>
+  ... ''')
+
+  >>> viewlet = zope.component.getMultiAdapter(
+  ...     (content, request, view, manager), interfaces.IViewlet, name='sport')
+  >>> viewlet()
+  u'Red Sox vs. White Sox'
+
+It should also be possible to specify an alternative attribute of the class to
+be rendered upon calling the viewlet:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="stock"
+  ...       for="*"
+  ...       manager="Products.Five.viewlet.tests.ILeftColumn"
+  ...       class="Products.Five.viewlet.tests.Stock"
+  ...       attribute="getStockTicker"
+  ...       permission="zope.Public"
+  ...       />
+  ... </configure>
+  ... ''')
+
+  >>> viewlet = zope.component.getMultiAdapter(
+  ...     (content, request, view, manager), interfaces.IViewlet,
+  ...     name='stock')
+  >>> viewlet.render()
+  u'SRC $5.19'
+
+A final feature the ``viewlet`` directive supports is the additional
+specification of any amount keyword arguments:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="stock2"
+  ...       permission="zope.Public"
+  ...       manager="Products.Five.viewlet.tests.ILeftColumn"
+  ...       class="Products.Five.viewlet.tests.Stock"
+  ...       weight="8"
+  ...       />
+  ... </configure>
+  ... ''')
+
+  >>> viewlet = zope.component.getMultiAdapter(
+  ...     (content, request, view, manager), interfaces.IViewlet,
+  ...     name='stock2')
+  >>> viewlet.weight
+  u'8'
+
+
+Error Scenarios
+---------------
+
+Neither the class or template have been specified:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="testviewlet"
+  ...       manager="Products.Five.viewlet.tests.ILeftColumn"
+  ...       permission="zope.Public"
+  ...       />
+  ... </configure>
+  ... ''')
+  Traceback (most recent call last):
+  ...
+  ZopeXMLConfigurationError: File "<string>", line 3.2-7.8
+      ConfigurationError: Must specify a class or template
+
+The specified attribute is not ``__call__``, but also a template has been
+specified:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="testviewlet"
+  ...       manager="Products.Five.viewlet.tests.ILeftColumn"
+  ...       template="test_viewlet.pt"
+  ...       attribute="faux"
+  ...       permission="zope.Public"
+  ...       />
+  ... </configure>
+  ... ''')
+  Traceback (most recent call last):
+  ...
+  ZopeXMLConfigurationError: File "<string>", line 3.2-9.8
+      ConfigurationError: Attribute and template cannot be used together.
+
+Now, we are not specifying a template, but a class that does not have the
+specified attribute:
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="testviewlet"
+  ...       manager="Products.Five.viewlet.tests.ILeftColumn"
+  ...       class="Products.Five.viewlet.tests.Sport"
+  ...       attribute="faux"
+  ...       permission="zope.Public"
+  ...       />
+  ... </configure>
+  ... ''')
+  Traceback (most recent call last):
+  ...
+  ZopeXMLConfigurationError: File "<string>", line 3.2-9.8
+    ConfigurationError: The provided class doesn't have the specified attribute
+
+================================
+Viewlet Directive Security
+================================
+
+Before we can begin, we need to set up a few things.  We need a
+manager account:
+
+  >>> uf = self.folder.acl_users
+  >>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
+
+Finally, we need to setup a traversable folder.  Otherwise, Five won't
+get do its view lookup magic:
+
+  >>> from OFS.Folder import manage_addFolder
+  >>> manage_addFolder(self.folder, 'ftf')
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/five" i18n_domain="zope">
+  ...   <traversable class="OFS.Folder.Folder"
+  ...       />
+  ... </configure>
+  ... ''')
+
+Now we can register another simple viewlet manager:
+
+  >>> from Products.Five.viewlet.tests import INewColumn
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewletManager
+  ...       name="newcolumn"
+  ...       permission="zope.Public"
+  ...       provides="Products.Five.viewlet.tests.INewColumn"
+  ...       />
+  ... </configure>
+  ... ''')
+
+And a view to call our new content provider:
+
+  >>> testTemplate = os.path.join(temp_dir, 'test.pt')
+  >>> open(testTemplate, 'w').write('''
+  ... <html>
+  ...   <body>
+  ...     <h1>Weather</h1>
+  ...     <div tal:content="structure provider:newcolumn" />
+  ...   </body>
+  ... </html>
+  ... ''')
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <page
+  ...       for="*"
+  ...       name="securitytest_view"
+  ...       template="%s"
+  ...       permission="zope.Public"
+  ...       />
+  ... </configure>
+  ... ''' % testTemplate)
+
+
+We now register some viewlets with different permissions:
+
+  >>> weatherTemplate = os.path.join(temp_dir, 'weather2.pt')
+  >>> open(weatherTemplate, 'w').write('''
+  ... <div>sunny</div>
+  ... ''')
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="weather3"
+  ...       manager="Products.Five.viewlet.tests.INewColumn"
+  ...       template="%s"
+  ...       permission="zope.Public"
+  ...       extra_string_attributes="can be specified"
+  ...       />
+  ... </configure>
+  ... ''' % weatherTemplate)
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="weather4"
+  ...       manager="Products.Five.viewlet.tests.INewColumn"
+  ...       template="%s"
+  ...       permission="zope2.ViewManagementScreens"
+  ...       />
+  ... </configure>
+  ... ''' % weatherTemplate)
+
+If we make the request as a manager, we should see both viewlets:
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/ftf/@@securitytest_view HTTP/1.1
+  ... Authorization: Basic manager:r00t
+  ... """, handle_errors=False)
+  HTTP/1.1 200 OK
+  ...
+       <h1>Weather</h1>
+       <div>
+       <div>sunny</div>
+       <div>sunny</div>
+       </div>
+  ...
+
+But when we make an anonymous request, we will only see the public viewlet:
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/ftf/@@securitytest_view HTTP/1.1
+  ... """, handle_errors=False)
+  HTTP/1.1 200 OK
+  ...
+       <h1>Weather</h1>
+       <div>
+       <div>sunny</div>
+       </div>
+  ...
+
+Cleanup
+-------
+
+  >>> import shutil
+  >>> shutil.rmtree(temp_dir)

Added: Products.Five/branches/alecm-viewlet-support/viewlet/javascript_viewlet.pt
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/javascript_viewlet.pt	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/javascript_viewlet.pt	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,3 @@
+<script type="text/javascript" src="some-library.js"
+        tal:attributes="src view/getURL">
+</script>

Added: Products.Five/branches/alecm-viewlet-support/viewlet/manager.py
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/manager.py	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/manager.py	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,65 @@
+import Acquisition
+from AccessControl.ZopeGuards import guarded_hasattr
+import zope.interface
+import zope.security
+from zope.viewlet import interfaces
+from zope.viewlet.manager import ViewletManagerBase as origManagerBase
+
+from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
+
+
+class ViewletManagerBase(origManagerBase, Acquisition.Explicit):
+    """A base class for Viewlet managers to work in Zope2"""
+
+    def __getitem__(self, name):
+        """See zope.interface.common.mapping.IReadMapping"""
+        # Find the viewlet
+        viewlet = zope.component.queryMultiAdapter(
+            (self.context, self.request, self.__parent__, self),
+            interfaces.IViewlet, name=name)
+
+        # If the viewlet was not found, then raise a lookup error
+        if viewlet is None:
+            raise zope.component.interfaces.ComponentLookupError(
+                'No provider with name `%s` found.' %name)
+
+        # If the viewlet cannot be accessed, then raise an
+        # unauthorized error
+        if not guarded_hasattr(viewlet.__of__(viewlet.context), 'render'):
+            raise zope.security.interfaces.Unauthorized(
+                'You are not authorized to access the provider '
+                'called `%s`.' %name)
+
+        # Return the viewlet.
+        return viewlet
+
+    def filter(self, viewlets):
+        """Sort out all content providers
+
+        ``viewlets`` is a list of tuples of the form (name, viewlet).
+        """
+        # Only return viewlets accessible to the principal
+        # We need to wrap each viewlet in its context to make sure that
+        # the object has a real context from which to determine owner
+        # security.
+        return [(name, viewlet) for name, viewlet in viewlets if
+                guarded_hasattr(viewlet.__of__(viewlet.context), 'render')]
+
+def ViewletManager(name, interface, template=None, bases=()):
+
+    if template is not None:
+        template = ZopeTwoPageTemplateFile(template)
+
+    if ViewletManagerBase not in bases:
+        # Make sure that we do not get a default viewlet manager mixin, if the
+        # provided base is already a full viewlet manager implementation.
+        if not (len(bases) == 1 and
+                interfaces.IViewletManager.implementedBy(bases[0])):
+            bases = bases + (ViewletManagerBase,)
+
+    ViewletManager = type(
+        '<ViewletManager providing %s>' % interface.getName(),
+        bases,
+        {'template': template, '__name__': name})
+    zope.interface.classImplements(ViewletManager, interface)
+    return ViewletManager

Added: Products.Five/branches/alecm-viewlet-support/viewlet/meta.zcml
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/meta.zcml	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/meta.zcml	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,21 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:meta="http://namespaces.zope.org/meta">
+
+  <meta:directives namespace="http://namespaces.zope.org/browser">
+
+    <meta:directive
+        name="viewlet"
+        schema="zope.viewlet.metadirectives.IViewletDirective"
+        handler=".metaconfigure.viewletDirective"
+        />
+
+    <meta:directive
+        name="viewletManager"
+        schema="zope.viewlet.metadirectives.IViewletManagerDirective"
+        handler=".metaconfigure.viewletManagerDirective"
+        />
+
+  </meta:directives>
+
+</configure>
\ No newline at end of file

Added: Products.Five/branches/alecm-viewlet-support/viewlet/metaconfigure.py
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/metaconfigure.py	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/metaconfigure.py	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,177 @@
+import os
+from zope.configuration.exceptions import ConfigurationError
+from zope.viewlet import interfaces
+from zope.interface import Interface
+from zope.interface import classImplements
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+from zope.app.component import metaconfigure
+from zope.app.publisher.browser import viewmeta
+from zope.app.publisher.interfaces.browser import IBrowserView
+
+from Products.Five.security import getSecurityInfo, protectClass, protectName
+import viewlet
+import manager
+
+
+from Globals import InitializeClass as initializeClass
+
+def viewletManagerDirective(
+    _context, name, permission,
+    for_=Interface, layer=IDefaultBrowserLayer, view=IBrowserView,
+    provides=interfaces.IViewletManager, class_=None, template=None,
+    allowed_interface=None, allowed_attributes=None):
+
+    # If class is not given we use the basic viewlet manager.
+    if class_ is None:
+        class_ = manager.ViewletManagerBase
+
+    # Iterate over permissions
+    if allowed_attributes is None:
+        allowed_attributes = ['render', 'update']
+    if allowed_interface is not None:
+        for interface in allowed_interface:
+            allowed_attributes.extend(interface.names())
+
+    # Make sure that the template exists and that all low-level API methods
+    # have the right permission.
+    if template:
+        template = os.path.abspath(str(_context.path(template)))
+        if not os.path.isfile(template):
+            raise ConfigurationError("No such file", template)
+        allowed_attributes.append('__getitem__')
+
+        # Create a new class based on the template and class.
+        new_class = manager.ViewletManager(
+            name, provides, template=template, bases=(class_, ))
+    else:
+        # Create a new class based on the class.
+        new_class = manager.ViewletManager(name, provides, bases=(class_, ))
+
+    # Register interfaces
+    viewmeta._handle_for(_context, for_)
+    metaconfigure.interface(_context, view)
+
+    # register a viewlet manager
+    _context.action(
+        discriminator = ('viewletManager', for_, layer, view, name),
+        callable = metaconfigure.handler,
+        args = ('provideAdapter',
+                (for_, layer, view), provides, name,
+                 new_class, _context.info),)
+    _context.action(
+        discriminator = ('five:protectClass', new_class),
+        callable = protectClass,
+        args = (new_class, permission)
+        )
+    if allowed_attributes:
+        for attr in allowed_attributes:
+            _context.action(
+                discriminator = ('five:protectName', new_class, attr),
+                callable = protectName,
+                args = (new_class, attr, permission)
+                )
+    _context.action(
+        discriminator = ('five:initialize:class', new_class),
+        callable = initializeClass,
+        args = (new_class,)
+        )
+
+
+def viewletDirective(
+    _context, name, permission,
+    for_=Interface, layer=IDefaultBrowserLayer, view=IBrowserView,
+    manager=interfaces.IViewletManager, class_=None, template=None,
+    attribute='render', allowed_interface=None, allowed_attributes=None,
+    **kwargs):
+
+    # Either the class or template must be specified.
+    if not (class_ or template):
+        raise ConfigurationError("Must specify a class or template")
+
+    # Make sure that all the non-default attribute specifications are correct.
+    if attribute != 'render':
+        if template:
+            raise ConfigurationError(
+                "Attribute and template cannot be used together.")
+
+        # Note: The previous logic forbids this condition to evere occur.
+        if not class_:
+            raise ConfigurationError(
+                "A class must be provided if attribute is used")
+
+    # Iterate over permissions
+    if allowed_attributes is None:
+        allowed_attributes = ['render', 'update']
+    if allowed_interface is not None:
+        for interface in allowed_interface:
+            allowed_attributes.extend(interface.names())
+
+    # Make sure that the template exists and that all low-level API methods
+    # have the right permission.
+    if template:
+        template = os.path.abspath(str(_context.path(template)))
+        if not os.path.isfile(template):
+            raise ConfigurationError("No such file", template)
+        allowed_attributes.append('__getitem__')
+
+    # Make sure the has the right form, if specified.
+    if class_:
+        if attribute != 'render':
+            if not hasattr(class_, attribute):
+                raise ConfigurationError(
+                    "The provided class doesn't have the specified attribute "
+                    )
+        if template:
+            # Create a new class for the viewlet template and class.
+            new_class = viewlet.SimpleViewletClass(
+                template, bases=(class_, ), attributes=kwargs)
+        else:
+            if not hasattr(class_, 'browserDefault'):
+                cdict = {'browserDefault':
+                         lambda self, request: (getattr(self, attribute), ())}
+            else:
+                cdict = {}
+
+            cdict['__name__'] = name
+            cdict['__page_attribute__'] = attribute
+            cdict.update(kwargs)
+            new_class = type(class_.__name__,
+                             (class_, viewlet.SimpleAttributeViewlet), cdict)
+
+        if hasattr(class_, '__implements__'):
+            classImplements(new_class, IBrowserPublisher)
+
+    else:
+        # Create a new class for the viewlet template alone.
+        new_class = viewlet.SimpleViewletClass(template, name=name,
+                                               attributes=kwargs)
+
+    # Register the interfaces.
+    viewmeta._handle_for(_context, for_)
+    metaconfigure.interface(_context, view)
+
+    # register viewlet
+    _context.action(
+        discriminator = ('viewlet', for_, layer, view, manager, name),
+        callable = metaconfigure.handler,
+        args = ('provideAdapter',
+                (for_, layer, view, manager), interfaces.IViewlet,
+                 name, new_class, _context.info),)
+
+    _context.action(
+        discriminator = ('five:protectClass', new_class),
+        callable = protectClass,
+        args = (new_class, permission)
+        )
+    if allowed_attributes:
+        for attr in allowed_attributes:
+            _context.action(
+                discriminator = ('five:protectName', new_class, attr),
+                callable = protectName,
+                args = (new_class, attr, permission)
+                )
+    _context.action(
+        discriminator = ('five:initialize:class', new_class),
+        callable = initializeClass,
+        args = (new_class,)
+        )
\ No newline at end of file

Added: Products.Five/branches/alecm-viewlet-support/viewlet/tests.py
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/tests.py	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/tests.py	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,131 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Viewlet tests
+
+$Id: tests.py 39461 2005-10-15 10:45:13Z srichter $
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from Testing.ZopeTestCase import FunctionalDocFileSuite
+from zope.app.testing import setup, ztapi
+from zope.component import provideAdapter
+from zope.interface import Interface
+from zope.interface import implements
+from Products.Five.traversable import FiveTraversable
+from zope.app.traversing.adapters import Traverser
+from zope.app.traversing.interfaces import ITraversable
+from zope.app.traversing.interfaces import ITraverser
+from zope.app.traversing.namespace import resource
+from zope.viewlet import interfaces
+from OFS.SimpleItem import SimpleItem
+
+class Content(SimpleItem):
+    implements(Interface)
+
+class UnitTestSecurityPolicy:
+    """
+        Stub out the existing security policy for unit testing purposes.
+    """
+    #
+    #   Standard SecurityPolicy interface
+    #
+    def validate( self
+                , accessed=None
+                , container=None
+                , name=None
+                , value=None
+                , context=None
+                , roles=None
+                , *args
+                , **kw):
+        return 1
+
+    def checkPermission( self, permission, object, context) :
+        return 1
+
+
+class ILeftColumn(interfaces.IViewletManager):
+    """Left column of my page."""
+
+
+class INewColumn(interfaces.IViewletManager):
+    """Left column of my page."""
+
+
+class WeightBasedSorting(object):
+    def sort(self, viewlets):
+        return sorted(viewlets,
+                      lambda x, y: cmp(x[1].weight, y[1].weight))
+
+
+class Weather(object):
+    weight = 0
+
+
+class Stock(object):
+    weight = 0
+    def getStockTicker(self):
+        return u'SRC $5.19'
+
+class Sport(object):
+    weight = 0
+    def __call__(self):
+        return u'Red Sox vs. White Sox'
+
+
+def setUp(test):
+    setup.placefulSetUp()
+
+#    # resource namespace setup
+#    from zope.app.traversing.interfaces import ITraversable
+#    from zope.app.traversing.namespace import resource
+#    ztapi.provideAdapter(None, ITraversable, resource, name="resource")
+#    ztapi.provideView(None, None, ITraversable, "resource", resource)
+#
+#    from zope.app.pagetemplate import metaconfigure
+#    from zope.contentprovider import tales
+#    metaconfigure.registerType('provider', tales.TALESProviderExpression)
+#
+#    zope.security.management.getInteraction().add(TestParticipation())
+
+#def directivesSetUp(test):
+#    setUp(test)
+#    setup.setUpTestAsModule(test, 'zope.viewlet.directives')
+
+
+def tearDown(test):
+    setup.placefulTearDown()
+
+#def directivesTearDown(test):
+#    tearDown(test)
+#    setup.tearDownTestAsModule(test)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        FunctionalDocFileSuite('README.txt',
+                     setUp=setUp, tearDown=tearDown
+                     ),
+        FunctionalDocFileSuite('directives.txt',
+                     setUp=setUp, tearDown=tearDown
+                     ),
+#        DocFileSuite('directives.txt',
+#                     setUp=directivesSetUp, tearDown=directivesTearDown,
+#                     optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+#                     ),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: Products.Five/branches/alecm-viewlet-support/viewlet/viewlet.py
===================================================================
--- Products.Five/branches/alecm-viewlet-support/viewlet/viewlet.py	2006-05-01 03:43:53 UTC (rev 67779)
+++ Products.Five/branches/alecm-viewlet-support/viewlet/viewlet.py	2006-05-01 05:02:46 UTC (rev 67780)
@@ -0,0 +1,68 @@
+import os, sys
+from Acquisition import Explicit
+from zope.viewlet import interfaces
+from zope.viewlet import viewlet as orig_viewlet
+
+from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
+
+# We add Acquisition to all the base classes to enable security machinery
+class ViewletBase(orig_viewlet.ViewletBase, Explicit):
+    pass
+
+class SimpleAttributeViewlet(orig_viewlet.SimpleAttributeViewlet, Explicit):
+    pass
+
+class simple(orig_viewlet.simple):
+    # We need to ensure that the proper __init__ is called.
+    __init__ = ViewletBase.__init__.im_func
+
+def SimpleViewletClass(template, bases=(), attributes=None,
+                       name=u''):
+    """A function that can be used to generate a viewlet from a set of
+    information.
+    """
+
+    # Create the base class hierarchy
+    bases += (simple, ViewletBase)
+
+    attrs = {'index' : ZopeTwoPageTemplateFile(template),
+             '__name__' : name}
+    if attributes:
+        attrs.update(attributes)
+
+    # Generate a derived view class.
+    class_ = type("SimpleViewletClass from %s" % template, bases, attrs)
+
+    return class_
+
+
+class ResourceViewletBase(orig_viewlet.ResourceViewletBase, Explicit):
+    pass
+
+def JavaScriptViewlet(path):
+    """Create a viewlet that can simply insert a javascript link."""
+    src = os.path.join(os.path.dirname(__file__), 'javascript_viewlet.pt')
+
+    klass = type('JavaScriptViewlet',
+                 (ResourceViewletBase, ViewletBase),
+                  {'index': ZopeTwoPageTemplateFile(src),
+                   '_path': path})
+
+    return klass
+
+
+class CSSResourceViewletBase(orig_viewlet.CSSResourceViewletBase):
+    pass
+
+def CSSViewlet(path, media="all", rel="stylesheet"):
+    """Create a viewlet that can simply insert a javascript link."""
+    src = os.path.join(os.path.dirname(__file__), 'css_viewlet.pt')
+
+    klass = type('CSSViewlet',
+                 (CSSResourceViewletBase, ViewletBase),
+                  {'index': ZopeTwoPageTemplateFile(src),
+                   '_path': path,
+                   '_media':media,
+                   '_rel':rel})
+
+    return klass



More information about the Zope-Checkins mailing list