[Zope3-checkins]
SVN: Zope3/branches/roger-contentprovider/src/zope/viewlet/
Some initial work on the refactoring; we are getting there.
Stephan Richter
srichter at cosmos.phy.tufts.edu
Sat Oct 8 12:40:03 EDT 2005
Log message for revision 38968:
Some initial work on the refactoring; we are getting there.
Changed:
U Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt
U Zope3/branches/roger-contentprovider/src/zope/viewlet/css_viewlet.pt
U Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py
A Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py
D Zope3/branches/roger-contentprovider/src/zope/viewlet/tests/
A Zope3/branches/roger-contentprovider/src/zope/viewlet/tests.py
-=-
Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt 2005-10-08 16:27:57 UTC (rev 38967)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt 2005-10-08 16:40:03 UTC (rev 38968)
@@ -1,605 +1,305 @@
-========
-Viewlets
-========
+=============================
+Viewlets and Viewlet Managers
+=============================
-This package provides a framework to develop componentized Web GUI
-applications. Instead of describing the content of a page using a single
-template or static system of templates and METAL macros, page regions can be
-defined and are filled dynamically with content (viewlets) based on the setup
-of the application.
+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
-Getting Started
----------------
-Let's say we have simple two-column page. In the smaller left column, there
-are boxes filled with various pieces of information, such as news, latest
-software releases, etc. This content, however, generally changes depending on
-the view and the object being viewed.
+The Viewlet Manager
+-------------------
+In this implementation of viewlets, those regions are just content providers
+called viewlet managers that manage other content providers known as
+viewlets. Every viewlet manager can handle viewlets of a certain type:
-Regions
-~~~~~~~
+ >>> class ILeftColumnViewlet(interfaces.IViewlet):
+ ... """This is a viewlet located in the left column."""
-Instead of hard-coding the pieces of content in the left column in the page
-template or using macros, we simply define a region for it. Regions are
-interfaces that act as content placeholders. Here is a common setup:
+You can then create a viewlet manager for this viewlet type:
- >>> import zope.interface
- >>> class ILeftColumn(zope.interface.Interface):
- ... '''The left column of a Web site.'''
+ >>> from zope.viewlet import manager
+ >>> leftColumn = manager.ViewletManager(ILeftColumnViewlet)
- >>> from zope.contentprovider.interfaces import IRegion
- >>> zope.interface.directlyProvides(ILeftColumn, IRegion)
+So initially nothing gets rendered:
- >>> import zope.component
- >>> zope.component.provideUtility(ILeftColumn, IRegion,
- ... 'webpage.LeftColumn')
+ >>> leftColumn()
+ u''
-It is important that the region interface provides the ``IRegion``
-interface and that it is registered as a utility providing
-``IRegion``. If omitted, the framework will be unable to find the
-region later.
+But now we register some viewlets for the manager
-
-Viewlet
-~~~~~~~
-
-Viewlets are snippets of content that can be placed into a region, such as the
-one defined above. As the name suggests, viewlets are views, but they are
-qualified not only by the context object and the request, but also the view
-they appear in. Also, the viewlet must *provide* the region interface it is
-filling; we will demonstrate a more advanced example later, where the purpose
-of this requirement becomes clear.
-
-Like regular views, viewlets can either use page templates to provide content
-or provide a simple ``__call__`` method. For our first viewlet, let's develop
-a more commonly used page-template-driven viewlet:
-
- >>> import os, tempfile
- >>> temp_dir = tempfile.mkdtemp()
-
- >>> viewletFileName = os.path.join(temp_dir, 'viewlet.pt')
- >>> open(viewletFileName, 'w').write('''
- ... <div class="box">
- ... <tal:block replace="viewlet/title" />
- ... </div>
- ... ''')
-
- >>> class ViewletBase(object):
- ... def title(self):
- ... return 'Viewlet Title'
-
-As you can see, the viewlet Python object is known as ``viewlet`` inside the
-template, while the view object is still available as ``view``. Next we build
-and register the viewlet using a special helper function:
-
- # Create the viewlet class
- >>> from zope.viewlet import viewlet
- >>> Viewlet = viewlet.SimpleViewletClass(
- ... viewletFileName, bases=(ViewletBase,), name='viewlet')
-
- # Generate a viewlet checker
- >>> from zope.security.checker import NamesChecker, defineChecker
- >>> viewletChecker = NamesChecker(('__call__', 'weight', 'title',))
- >>> defineChecker(Viewlet, viewletChecker)
-
- # Register the viewlet with component architecture
+ >>> import zope.component
>>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
>>> from zope.app.publisher.interfaces.browser import IBrowserView
- >>> zope.component.provideAdapter(
- ... Viewlet,
- ... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
- ... ILeftColumn,
- ... name='viewlet')
-As you can see from the security checker registration, a viewlet provides also
-a weight, which acts as a hint to determine the order in which the viewlets of
-a region should be displayed. The view the viewlet is used in can also be
-accessed via the ``view`` attribute of the viewlet class.
-
-
-Creating the View
-~~~~~~~~~~~~~~~~~
-
-Now that we have a region with a viewlet registered for it, let's use it by
-creating the front page of our Web Site:
-
- >>> templateFileName = os.path.join(temp_dir, 'template.pt')
- >>> open(templateFileName, 'w').write('''
- ... <html>
- ... <body>
- ... <h1>My Web Page</h1>
- ... <div class="left-column">
- ... <div class="column-item"
- ... tal:repeat="viewlet providers:webpage.LeftColumn">
- ... <tal:block replace="structure viewlet" />
- ... </div>
- ... </div>
- ... <div class="main">
- ... Content here
- ... </div>
- ... </body>
- ... </html>
- ... ''')
-
-and registering it as a view (browser page) for all objects:
-
- >>> from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
- >>> FrontPage = SimpleViewClass(templateFileName, name='main.html')
-
- >>> zope.component.provideAdapter(
- ... FrontPage,
- ... (zope.interface.Interface, IDefaultBrowserLayer),
- ... zope.interface.Interface,
- ... name='main.html')
-
-That is all of the setup. Let's now render the view.
-
-
-Using the View
-~~~~~~~~~~~~~~
-
-Let's create a content object that can be viewed:
-
- >>> class Content(object):
- ... zope.interface.implements(zope.interface.Interface)
-
- >>> content = Content()
-
-Finally we look up the view and render it:
-
- >>> from zope.publisher.browser import TestRequest
- >>> request = TestRequest()
-
- >>> view = zope.component.getMultiAdapter((content, request),
- ... name='main.html')
- >>> print view().strip()
- <html>
- <body>
- <h1>My Web Page</h1>
- <div class="left-column">
- <div class="column-item">
- <BLANKLINE>
- <div class="box">
- Viewlet Title
- </div>
- <BLANKLINE>
- </div>
- </div>
- <div class="main">
- Content here
- </div>
- </body>
- </html>
-
-
-Class-driven Viewlets
-~~~~~~~~~~~~~~~~~~~~~
-
-Let's now have a look into the steps involved to create a viewlet class from
-scratch. We also want to ensure that this viewlet always displays second and
-not before the first one. Here is a most simple implementation:
-
- >>> from zope.app.publisher.browser import BrowserView
- >>> from zope.viewlet.interfaces import IViewlet
- >>> class InfoViewlet(BrowserView):
- ... zope.interface.implements(IViewlet, ILeftColumn)
- ... weight = 1
+ >>> class WeatherBox(ILeftColumnViewlet):
...
... def __init__(self, context, request, view):
- ... super(InfoViewlet, self).__init__(context, request)
- ... self.view = view
+ ... pass
...
... def __call__(self):
- ... return u'<h3>Some Information.</h3>'
+ ... return u'<div class="box">It is sunny today!</div>'
- >>> defineChecker(InfoViewlet, viewletChecker)
-
>>> zope.component.provideAdapter(
- ... InfoViewlet,
+ ... WeatherBox,
... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
- ... ILeftColumn,
- ... name='infoViewlet')
+ ... ILeftColumnViewlet, name='weather')
-
-Note that you would commonly not state in the class itself that it
-implements a particular region, since it is usually done by the ZCML
-directive, which is introduced in `directives.zcml`.
-
-When we now render the view, the content of our info viewlet appears as well:
-
- >>> print view().strip()
- <html>
- <body>
- <h1>My Web Page</h1>
- <div class="left-column">
- <div class="column-item">
- <BLANKLINE>
- <div class="box">
- Viewlet Title
- </div>
- <BLANKLINE>
- </div>
- <div class="column-item">
- <h3>Some Information.</h3>
- </div>
- </div>
- <div class="main">
- Content here
- </div>
- </body>
- </html>
-
-
-Changing the Weight
-~~~~~~~~~~~~~~~~~~~
-
-Let's ensure that the weight really affects the order of the viewlets. If we
-change the weights around,
-
- >>> InfoViewlet.weight = 0
- >>> Viewlet._weight = 1
-
-the order of the left column in the page template should change:
-
- >>> print view().strip()
- <html>
- <body>
- <h1>My Web Page</h1>
- <div class="left-column">
- <div class="column-item">
- <h3>Some Information.</h3>
- </div>
- <div class="column-item">
- <BLANKLINE>
- <div class="box">
- Viewlet Title
- </div>
- <BLANKLINE>
- </div>
- </div>
- <div class="main">
- Content here
- </div>
- </body>
- </html>
-
-
-Looking Up a Viewlet by Name
-----------------------------
-
-In some cases you want to be able to look up a particular viewlet for a region,
-given a context and a view. For this use case, you can simply use a second
-TALES namespace called ``viewlet`` that selects the viewlet using the
-expression ``<path to region>/<viewlet name>``.
-
-Since everything else is already set up, we can simply register a new view:
-
- >>> template2FileName = os.path.join(temp_dir, 'template2.pt')
- >>> open(template2FileName, 'w').write('''
- ... <html>
- ... <body>
- ... <h1>My Web Page - Take 2</h1>
- ... <div class="left-column">
- ... <div class="column-item">
- ... <tal:block
- ... replace="structure provider:webpage.LeftColumn/viewlet" />
- ... </div>
- ... </div>
- ... </body>
- ... </html>
- ... ''')
-
- >>> SecondPage = SimpleViewClass(template2FileName, name='second.html')
- >>> zope.component.provideAdapter(
- ... SecondPage,
- ... (zope.interface.Interface, IDefaultBrowserLayer),
- ... ILeftColumn,
- ... name='second.html')
-
- >>> view = zope.component.getMultiAdapter((content, request),
- ... name='second.html')
- >>> print view().strip()
- <html>
- <body>
- <h1>My Web Page - Take 2</h1>
- <div class="left-column">
- <div class="column-item">
- <BLANKLINE>
- <div class="box">
- Viewlet Title
- </div>
- <BLANKLINE>
- </div>
- </div>
- </body>
- </html>
-
-Note that this namespace returns the rendered viewlet and not the viewlet
-view, like the ``viewlets`` TALES namespace.
-
-
-Region Schemas
---------------
-
-In some use cases you want to be able to provide variables to a viewlet that
-cannot be accessed via the view class or the context object. They are usually
-variables that are defined by the view template. Since we do not just want all
-of the view's template variables to be available (because it would be implicit
-and not all viewlets must be called from within page templates), we must
-specify the variables that the environment of the viewlet provides in the slot
-interface as fields.
-
-Let's say in your view you want to display a list of objects and you would
-like to allow various columns that are controlled by viewlets:
-
- >>> class ObjectItems(object):
+ >>> class SportBox(ILeftColumnViewlet):
...
- ... def objectInfo(self):
- ... return [{'name': 'README.txt', 'size': '1.2kB'},
- ... {'name': 'logo.png', 'size': '100 x 100'}]
-
- >>> contentsFileName = os.path.join(temp_dir, 'items.pt')
- >>> open(contentsFileName, 'w').write('''
- ... <html>
- ... <body>
- ... <h1>Contents</h1>
- ... <table>
- ... <tr tal:repeat="item view/objectInfo">
- ... <td tal:repeat="column providers:webpage.ObjectInfoColumn"
- ... tal:content="structure column" />
- ... </tr>
- ... </table>
- ... </body>
- ... </html>
- ... ''')
-
- >>> Contents = SimpleViewClass(contentsFileName, bases=(ObjectItems,),
- ... name='contents.html')
-
- >>> zope.component.provideAdapter(
- ... Contents,
- ... (zope.interface.Interface, IDefaultBrowserLayer),
- ... zope.interface.Interface,
- ... name='contents.html')
-
-As you can see from the page template code, in order for the viewlets to be
-of any use, they need access to the ``item`` variable as defined in the page
-template. Thus, the region definition will state that the viewlet must have
-access to a variable called ``item`` that contains the value of ``item`` in
-the page template:
-
- >>> import zope.schema
- >>> class IObjectInfoColumn(zope.interface.Interface):
- ... '''Place holder for the columns of a contents view.'''
- ...
- ... item = zope.schema.Dict(
- ... title=u'Object info dictionary',
- ... required=True)
-
- >>> zope.interface.directlyProvides(
- ... IObjectInfoColumn, IRegion)
-
- >>> zope.component.provideUtility(
- ... IObjectInfoColumn, IRegion, 'webpage.ObjectInfoColumn')
-
-
-Next we implement two very simple viewlets, one displaying the name
-
- >>> class NameColumnViewlet(BrowserView):
- ... zope.interface.implements(IViewlet, IObjectInfoColumn)
- ... weight = 0
- ...
... def __init__(self, context, request, view):
- ... super(NameColumnViewlet, self).__init__(context, request)
- ... self.view = view
+ ... pass
...
... def __call__(self):
- ... return '<b>' + self.item['name'] + '</b>'
+ ... return u'<div class="box">Patriots (23) : Steelers (7)</div>'
- >>> defineChecker(NameColumnViewlet, viewletChecker)
-
>>> zope.component.provideAdapter(
- ... NameColumnViewlet,
+ ... SportBox,
... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
- ... IObjectInfoColumn,
- ... name='name')
+ ... ILeftColumnViewlet, name='sport')
-... and the other displaying the size of the of objects in the list:
+and thus the left column is filled:
- >>> class SizeColumnViewlet(BrowserView):
- ... zope.interface.implements(IViewlet, IObjectInfoColumn)
- ... weight = 1
- ...
- ... def __init__(self, context, request, view):
- ... super(SizeColumnViewlet, self).__init__(context, request)
- ... self.view = view
- ...
- ... def __call__(self):
- ... return self.item['size']
+ >>> leftColumn()
- >>> defineChecker(SizeColumnViewlet, viewletChecker)
+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:
- >>> zope.component.provideAdapter(
- ... SizeColumnViewlet,
- ... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
- ... IObjectInfoColumn,
- ... name='size')
+ >>> 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 viewlets"
+ ... replace="structure viewlet" />
+ ... </div>
+ ... ''')
+ >>> leftColumn = manager.ViewletManager(ILeftColumnViewlet, leftColTemplate)
-Now let's have a look at the resulting view:
+As you can see, the viewlet manager provides a global ``viewlets`` variable
+that is an iterable of all the avialable viewlets in the correct order:
- >>> view = zope.component.getMultiAdapter(
- ... (content, request), name='contents.html')
- >>> print view().strip()
- <html>
- <body>
- <h1>Contents</h1>
- <table>
- <tr>
- <td><b>README.txt</b></td>
- <td>1.2kB</td>
- </tr>
- <tr>
- <td><b>logo.png</b></td>
- <td>100 x 100</td>
- </tr>
- </table>
- </body>
- </html>
+ >>> leftColumn()
+You can also lookup the viewlets directly for management purposes:
-Content Provider Managers
--------------------------
+ >>> leftColumn['weather']
+ <WeatherBox ...>
+ >>> leftColumn.get('weather')
+ <WeatherBox ...>
-Until now we have always asserted that the viewlets returned by the TALES
-namespaces ``viewlets`` and ``viewlet`` always find the viewlets in the
-component architecture and then return them ordered by weight. This, however,
-is just the default policy. We could also register an alternative policy that
-has different rules on looking up, filtering and sorting the viewlets. The
-objects that implement those policies are known as content provider managers.
+If the viewlet is not found, then the expected behavior is provided:
-These are usually implemented as adapters from the context, request
-and view to the ``IViewletManager`` interface. They must implement two
-methods. The first one is ``getViewlets(region)``, which returns a list of
-viewlets for the specified region. The region argument is the region
-interface. The second method is ``getViewlet(name, region)``, which allows you
-to look up a specific viewlet by name and region.
+ >>> leftColumn['stock']
+ >>> leftColumn.get('stock') is None
+ True
-An Alternative Content Provider Manager
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Let's now imagine that we would like to allow the user to choose the columns
-for the contents view. Here it would not be enough to implement a condition as
-part of the viewlet class, since the TD tag appearance is not controlled by
-the viewlet itself. In those cases it is best to implement a custom content
-provider manager that only returns the viewlets that are specified in an
-option:
+Viewlet Weight Support
+----------------------
- >>> showColumns = ['name', 'size']
-So our custom content provider manager could look something like this:
+A Complex Example
+-----------------
- >>> from zope.contentprovider import manager
- >>> from zope.contentprovider.interfaces import IContentProviderManager
- >>> class ContentsContentProviderManager(manager.DefaultContentProviderManager):
- ...
- ... def values(self):
- ... viewlets = zope.component.getAdapters(
- ... (self.context, self.request, self.view), self.region)
- ... viewlets = [(name, viewlet) for name, viewlet in viewlets
- ... if name in showColumns]
- ... viewlets.sort(lambda x, y: cmp(showColumns.index(x[0]),
- ... showColumns.index(y[0])))
- ... return [viewlet for name, viewlet in viewlets]
+#
+#Viewlet
+#~~~~~~~
+#
+#Viewlets are snippets of content that can be placed into a region, such as the
+#one defined above. As the name suggests, viewlets are views, but they are
+#qualified not only by the context object and the request, but also the view
+#they appear in. Also, the viewlet must *provide* the region interface it is
+#filling; we will demonstrate a more advanced example later, where the purpose
+#of this requirement becomes clear.
+#
+#Like regular views, viewlets can either use page templates to provide content
+#or provide a simple ``__call__`` method. For our first viewlet, let's develop
+#a more commonly used page-template-driven viewlet:
+#
+# >>> import os, tempfile
+# >>> temp_dir = tempfile.mkdtemp()
+#
+# >>> viewletFileName = os.path.join(temp_dir, 'viewlet.pt')
+# >>> open(viewletFileName, 'w').write('''
+# ... <div class="box">
+# ... <tal:block replace="viewlet/title" />
+# ... </div>
+# ... ''')
+#
+# >>> class ViewletBase(object):
+# ... def title(self):
+# ... return 'Viewlet Title'
+#
+#As you can see, the viewlet Python object is known as ``viewlet`` inside the
+#template, while the view object is still available as ``view``. Next we build
+#and register the viewlet using a special helper function:
+#
+# # Create the viewlet class
+# >>> from zope.viewlet import viewlet
+# >>> Viewlet = viewlet.SimpleViewletClass(
+# ... viewletFileName, bases=(ViewletBase,), name='viewlet')
+#
+# # Generate a viewlet checker
+# >>> from zope.security.checker import NamesChecker, defineChecker
+# >>> viewletChecker = NamesChecker(('__call__', 'weight', 'title',))
+# >>> defineChecker(Viewlet, viewletChecker)
+#
+# # Register the viewlet with component architecture
+# >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+# >>> from zope.app.publisher.interfaces.browser import IBrowserView
+# >>> zope.component.provideAdapter(
+# ... Viewlet,
+# ... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
+# ... ILeftColumn,
+# ... name='viewlet')
+#
+#As you can see from the security checker registration, a viewlet provides also
+#a weight, which acts as a hint to determine the order in which the viewlets of
+#a region should be displayed. The view the viewlet is used in can also be
+#accessed via the ``view`` attribute of the viewlet class.
+#
+#
+#Changing the Weight
+#~~~~~~~~~~~~~~~~~~~
+#
+#Let's ensure that the weight really affects the order of the viewlets. If we
+#change the weights around,
+#
+# >>> InfoViewlet.weight = 0
+# >>> Viewlet._weight = 1
+#
+#the order of the left column in the page template should change:
+#
+# >>> print view().strip()
+# <html>
+# <body>
+# <h1>My Web Page</h1>
+# <div class="left-column">
+# <div class="column-item">
+# <h3>Some Information.</h3>
+# </div>
+# <div class="column-item">
+# <BLANKLINE>
+# <div class="box">
+# Viewlet Title
+# </div>
+# <BLANKLINE>
+# </div>
+# </div>
+# <div class="main">
+# Content here
+# </div>
+# </body>
+# </html>
+#
+#
+#
+#An Alternative Content Provider Manager
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#Let's now imagine that we would like to allow the user to choose the columns
+#for the contents view. Here it would not be enough to implement a condition as
+#part of the viewlet class, since the TD tag appearance is not controlled by
+#the viewlet itself. In those cases it is best to implement a custom content
+#provider manager that only returns the viewlets that are specified in an
+#option:
+#
+# >>> showColumns = ['name', 'size']
+#
+#So our custom content provider manager could look something like this:
+#
+# >>> from zope.contentprovider import manager
+# >>> from zope.contentprovider.interfaces import IContentProviderManager
+# >>> class ContentsContentProviderManager(manager.DefaultContentProviderManager):
+# ...
+# ... def values(self):
+# ... viewlets = zope.component.getAdapters(
+# ... (self.context, self.request, self.view), self.region)
+# ... viewlets = [(name, viewlet) for name, viewlet in viewlets
+# ... if name in showColumns]
+# ... viewlets.sort(lambda x, y: cmp(showColumns.index(x[0]),
+# ... showColumns.index(y[0])))
+# ... return [viewlet for name, viewlet in viewlets]
+#
+#We just have to register it as an adapter:
+#
+# >>> zope.component.provideAdapter(
+# ... ContentsContentProviderManager,
+# ... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView,
+# ... IObjectInfoColumn),
+# ... IContentProviderManager)
+#
+# >>> view = zope.component.getMultiAdapter(
+# ... (content, request), name='contents.html')
+# >>> print view().strip()
+# <html>
+# <body>
+# <h1>Contents</h1>
+# <table>
+# <tr>
+# <td><b>README.txt</b></td>
+# <td>1.2kB</td>
+# </tr>
+# <tr>
+# <td><b>logo.png</b></td>
+# <td>100 x 100</td>
+# </tr>
+# </table>
+# </body>
+# </html>
+#
+#But if I turn the order around,
+#
+# >>> showColumns = ['size', 'name']
+#
+#it will provide the columns in a different order as well:
+#
+# >>> print view().strip()
+# <html>
+# <body>
+# <h1>Contents</h1>
+# <table>
+# <tr>
+# <td>1.2kB</td>
+# <td><b>README.txt</b></td>
+# </tr>
+# <tr>
+# <td>100 x 100</td>
+# <td><b>logo.png</b></td>
+# </tr>
+# </table>
+# </body>
+# </html>
+#
+#On the other hand, it is as easy to remove a column:
+#
+# >>> showColumns = ['name']
+# >>> print view().strip()
+# <html>
+# <body>
+# <h1>Contents</h1>
+# <table>
+# <tr>
+# <td><b>README.txt</b></td>
+# </tr>
+# <tr>
+# <td><b>logo.png</b></td>
+# </tr>
+# </table>
+# </body>
+# </html>
+#
+#
-We just have to register it as an adapter:
-
- >>> zope.component.provideAdapter(
- ... ContentsContentProviderManager,
- ... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView,
- ... IObjectInfoColumn),
- ... IContentProviderManager)
-
- >>> view = zope.component.getMultiAdapter(
- ... (content, request), name='contents.html')
- >>> print view().strip()
- <html>
- <body>
- <h1>Contents</h1>
- <table>
- <tr>
- <td><b>README.txt</b></td>
- <td>1.2kB</td>
- </tr>
- <tr>
- <td><b>logo.png</b></td>
- <td>100 x 100</td>
- </tr>
- </table>
- </body>
- </html>
-
-But if I turn the order around,
-
- >>> showColumns = ['size', 'name']
-
-it will provide the columns in a different order as well:
-
- >>> print view().strip()
- <html>
- <body>
- <h1>Contents</h1>
- <table>
- <tr>
- <td>1.2kB</td>
- <td><b>README.txt</b></td>
- </tr>
- <tr>
- <td>100 x 100</td>
- <td><b>logo.png</b></td>
- </tr>
- </table>
- </body>
- </html>
-
-On the other hand, it is as easy to remove a column:
-
- >>> showColumns = ['name']
- >>> print view().strip()
- <html>
- <body>
- <h1>Contents</h1>
- <table>
- <tr>
- <td><b>README.txt</b></td>
- </tr>
- <tr>
- <td><b>logo.png</b></td>
- </tr>
- </table>
- </body>
- </html>
-
-
-UML Diagram
------------
-
- _________
- | |
- | Context |
- |_________|
- ^
- |
- |*
- ____|____
- | |
- | View |
- |_________|
- |
- |
- |* a view is composed of regions
- ____v____
- | |
- | Region |
- |_________|
- |
- |
- |* a region contains a list of viewlets
- ____v____
- | |
- | Viewlet |
- |_________|
-
-Natively, Zope 3 allows us to associate one or more views to a given
-object. Those views are either registered for the provided interfaces of the
-object or the object itself. In a view, usually a template, one can define
-zero or more view regions. Upon rendering time, those view regions are populated
-with the viewlets that have been assigned to the region.
-
-
Cleanup
-------
Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/css_viewlet.pt
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/css_viewlet.pt 2005-10-08 16:27:57 UTC (rev 38967)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/css_viewlet.pt 2005-10-08 16:40:03 UTC (rev 38968)
@@ -1,4 +1,4 @@
<link type="text/css" rel="stylesheet" href="somestyle.css" media="all"
tal:attributes="rel viewlet/getRel;
- href viewlet/getURL;
+ href viewlet/getURL;
media viewlet/getMedia" />
Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py 2005-10-08 16:27:57 UTC (rev 38967)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py 2005-10-08 16:40:03 UTC (rev 38968)
@@ -17,61 +17,43 @@
"""
__docformat__ = 'restructuredtext'
-import zope.component
import zope.interface
import zope.schema
-from zope.tales import interfaces
-
from zope.app.i18n import ZopeMessageIDFactory as _
-from zope.app.publisher.interfaces.browser import IBrowserView
from zope.contentprovider.interfaces import IContentProvider
+class IViewlet(IContentProvider):
+ """A content provider that is managed by another content provider, known
+ as viewlet manager.
+ """
-class IViewlet(IBrowserView, IContentProvider):
- """A piece of content of a page.
- Viewlets are objects that can fill the region specified in a page, most
- often page templates. They are selected by the context, request and
- view. All viewlets of a particular region must also provide the region
- interface.
+class IViewletManager(IContentProvider, IReadMapping):
+ """An object that provides access to the content providers.
+
+ The viewlet manager's resposibilities are:
+
+ (1) Aggregation of all viewlets of a given type.
+
+ (2) Apply a set of filters to determine the availability of the
+ viewlets.
+
+ (3) Sort the viewlets based on some implemented policy.
"""
- view = zope.interface.Attribute(
- 'The view the viewlet is used in.')
+ providerType = zope.interface.Attribute(
+ '''The specific type of provider that are displayed by this manager.''')
+
+class IWeightSupport(zope.interface.Interface):
+ """Components implementing this interface are sortable by weight."""
+
weight = zope.schema.Int(
title=_(u'weight'),
description=_(u"""
- Key for sorting viewlets if the viewlet collector is supporting
- this sort mechanism."""),
+ Key for sorting viewlets if the viewlet manager is supporting this
+ sort mechanism."""),
required=False,
default=0)
-
-
-#class IViewletManager(zope.interface.Interface):
-# """An object that provides access to the viewlets.
-#
-# The viewlets are of a particular context, request and view configuration
-# are accessible via a particular manager instance. Viewlets are looked up
-# by the region they appear in and the name of the viewlet.
-# """
-#
-# context = zope.interface.Attribute(
-# 'The context of the view the viewlet appears in.')
-#
-# view = zope.interface.Attribute(
-# 'The view the viewlet is used in.')
-#
-# request = zope.interface.Attribute(
-# 'The request of the view the viewlet is used in.')
-#
-# def values(region):
-# """Get all available viewlets of the given region.
-#
-# This method is responsible for sorting the viewlets as well.
-# """
-#
-# def __getitem__(self, name, region):
-# """Get a particular viewlet of a region selected by name."""
Copied: Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py (from rev 38927, Zope3/branches/roger-contentprovider/src/zope/contentprovider/manager.py)
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/contentprovider/manager.py 2005-10-08 09:42:33 UTC (rev 38927)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py 2005-10-08 16:40:03 UTC (rev 38968)
@@ -0,0 +1,101 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Content Provider Manager implementation
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import zope.component
+import zope.interface
+import zope.security
+
+from zope.contentprovider import interfaces
+
+
+class ViewletManagerBase(object):
+ """The Viewlet Manager Base
+
+ A generic manager class which can be instantiated
+ """
+ zope.interface.implements(interfaces.IViewletManager)
+
+ providerType = None
+
+ def __init__(self, context, request, view):
+ self.context = context
+ self.request = request
+ self.view = view
+
+
+ def __getitem__(self, name):
+ """See zope.interface.common.mapping.IReadMapping"""
+ # Find the content provider
+ provider = zope.component.queryMultiAdapter(
+ (self.context, self.request, self.view), self.region, name=name)
+
+ # If the content provider was not found, then raise a lookup error
+ if provider is None:
+ raise zope.component.interfaces.ComponentLookupError(
+ 'No provider with name `%s` found.' %name)
+
+ # If the content provider cannot be accessed, then raise an
+ # unauthorized error
+ if not zope.security.canAccess(provider, '__call__'):
+ raise zope.security.interfaces.Unauthorized(
+ 'You are not authorized to access the provider '
+ 'called `%s`.' %name)
+
+ # Return the rendered content provider.
+ return provider
+
+
+ def get(self, name, default=None):
+ try:
+ return self[name]
+ except (zope.component.interfaces.ComponentLookupError,
+ zope.security.interfaces.Unauthorized):
+ return default
+
+
+ def __call__(self, *args, **kw)
+ """See zope.contentprovider.interfaces.IContentProvider"""
+
+ # Find all content providers for the region
+ viewlets = zope.component.getAdapters(
+ (self.context, self.request, self.view), self.viewType)
+
+ # Sort out all content providers that cannot be accessed by the
+ # principal
+ viewlets = [viewlet for name, viewlet in viewlets
+ if zope.security.canAccess(viewlet, '__call__')]
+
+ # Sort the content providers by weight.
+ if interfaces.IWeightSupport in self.viewletType.flattened():
+ viewlets.sort(lambda x, y: cmp(x.weight, y.weight))
+ else:
+ viewlets.sort()
+
+ # Now render the view
+ if self.template:
+ return self.template(viewlets=viewlets)
+ else:
+ return u'\n'.join(viewlets)
+
+
+def ViewletManager(type, template=None):
+
+ return type('<ViewletManager for %s>' %type.getName(),
+ (ViewletManagerBase,),
+ {'providerType': type, 'template': None})
Copied: Zope3/branches/roger-contentprovider/src/zope/viewlet/tests.py (from rev 38943, Zope3/branches/roger-contentprovider/src/zope/viewlet/tests/test_doc.py)
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/tests/test_doc.py 2005-10-08 12:55:12 UTC (rev 38943)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/tests.py 2005-10-08 16:40:03 UTC (rev 38968)
@@ -0,0 +1,63 @@
+##############################################################################
+#
+# 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$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+import zope.interface
+import zope.security
+from zope.testing import doctest
+from zope.testing.doctestunit import DocTestSuite, DocFileSuite
+from zope.app.testing import setup
+
+from zope.contentprovider.interfaces import IRegion
+from zope.viewlet import interfaces
+
+
+class TestParticipation(object):
+ principal = 'foobar'
+ interaction = None
+
+
+def setUp(test):
+ setup.placefulSetUp()
+
+ from zope.app.pagetemplate import metaconfigure
+ from zope.contentprovider import tales
+ metaconfigure.registerType('provider', tales.TALESProviderExpression)
+
+ zope.security.management.getInteraction().add(TestParticipation())
+
+
+def tearDown(test):
+ setup.placefulTearDown()
+
+
+def test_suite():
+ return unittest.TestSuite((
+ DocFileSuite('README.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ DocFileSuite('directives.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
More information about the Zope3-Checkins
mailing list