[Zope3-checkins]
SVN: Zope3/branches/roger-contentprovider/src/zope/viewlet/ Yep,
we refactored again! But I think we are nearing a very flexible
Stephan Richter
srichter at cosmos.phy.tufts.edu
Sun Oct 9 08:01:02 EDT 2005
Log message for revision 39005:
Yep, we refactored again! But I think we are nearing a very flexible
implementation now and I feel better about it every time.
Changed:
U Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt
U Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py
U Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py
-=-
Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt 2005-10-09 11:17:02 UTC (rev 39004)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt 2005-10-09 12:01:02 UTC (rev 39005)
@@ -14,16 +14,16 @@
-------------------
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:
+called viewlet managers that manage a special type of content providers known
+as viewlets. Every viewlet manager handles the viewlets registered for it:
- >>> class ILeftColumnViewlet(interfaces.IViewlet):
- ... """This is a viewlet located in the left column."""
+ >>> class ILeftColumn(interfaces.IViewletManager):
+ ... """Viewlet manager located in the left column."""
-You can then create a viewlet manager for this viewlet type:
+You can then create a viewlet manager using this interface now:
>>> from zope.viewlet import manager
- >>> LeftColumn = manager.ViewletManager(ILeftColumnViewlet)
+ >>> LeftColumn = manager.ViewletManager(ILeftColumn)
Now we have to instantiate it:
@@ -55,27 +55,29 @@
>>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
>>> class WeatherBox(object):
- ... zope.interface.implements(ILeftColumnViewlet)
+ ... zope.interface.implements(interfaces.IViewlet)
...
- ... def __init__(self, context, request, view):
+ ... def __init__(self, context, request, view, manager):
... pass
...
... def __call__(self):
... return u'<div class="box">It is sunny today!</div>'
+ # Create a security checker for viewlets.
>>> from zope.security.checker import NamesChecker, defineChecker
>>> viewletChecker = NamesChecker(('__call__', 'weight'))
>>> defineChecker(WeatherBox, viewletChecker)
>>> zope.component.provideAdapter(
... WeatherBox,
- ... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
- ... ILeftColumnViewlet, name='weather')
+ ... (zope.interface.Interface, IDefaultBrowserLayer,
+ ... IBrowserView, ILeftColumn),
+ ... interfaces.IViewlet, name='weather')
>>> class SportBox(object):
- ... zope.interface.implements(ILeftColumnViewlet)
+ ... zope.interface.implements(interfaces.IViewlet)
...
- ... def __init__(self, context, request, view):
+ ... def __init__(self, context, request, view, manager):
... pass
...
... def __call__(self):
@@ -85,8 +87,9 @@
>>> zope.component.provideAdapter(
... SportBox,
- ... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
- ... ILeftColumnViewlet, name='sport')
+ ... (zope.interface.Interface, IDefaultBrowserLayer,
+ ... IBrowserView, ILeftColumn),
+ ... interfaces.IViewlet, name='sport')
and thus the left column is filled:
@@ -109,12 +112,15 @@
... </div>
... ''')
- >>> LeftColumn = manager.ViewletManager(ILeftColumnViewlet, leftColTemplate)
+ >>> LeftColumn = manager.ViewletManager(ILeftColumn, template=leftColTemplate)
>>> leftColumn = LeftColumn(content, request, 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:
+XXX: 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:
+
>>> print leftColumn().strip()
<div class="left-column">
<div class="box">Patriots (23) : Steelers (7)</div>
@@ -138,42 +144,39 @@
>>> leftColumn.get('stock') is None
True
+Customizing the default Viewlet Manager
+---------------------------------------
-Viewlet Weight Support
-----------------------
+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.
-One important feature of any viewlet manager is to be able to sort the
-viewlets it is displaying. The default viewlet manager that we have been using
-in the tests above, supports sorting via the viewlet's ``__cmp__()`` method
-(default) or sorting by weight. To make this work, the provider type interface
-must inherit the ``IWeightSupport`` interface:
+In our case we will manage the viewlets using a global list:
- >>> class IWeightedLeftColumnViewlet(interfaces.IViewlet,
- ... interfaces.IWeightSupport):
- ... """This is a viewlet located in the left column."""
+ >>> shown = ['weather', 'sport']
-This means we also need to change the provider type interface in the viewlet
-manager:
+The viewlet manager base class now uses this list:
- >>> leftColumn.providerType = IWeightedLeftColumnViewlet
+ >>> 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]
-Now we assign the weight to the viewlets and ensure that the interface is
-implemented and the viewlets are registered for this interface:
+Let's now create a new viewlet manager:
- >>> WeatherBox.weight = 0
- >>> zope.interface.classImplements(WeatherBox, IWeightedLeftColumnViewlet)
- >>> zope.component.provideAdapter(
- ... WeatherBox,
- ... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
- ... IWeightedLeftColumnViewlet, name='weather')
+ >>> LeftColumn = manager.ViewletManager(
+ ... ILeftColumn, bases=(ListViewletManager,), template=leftColTemplate)
+ >>> leftColumn = LeftColumn(content, request, view)
- >>> SportBox.weight = 1
- >>> zope.interface.classImplements(SportBox, IWeightedLeftColumnViewlet)
- >>> zope.component.provideAdapter(
- ... SportBox,
- ... (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
- ... IWeightedLeftColumnViewlet, name='sport')
-
So we get the weather box first and the sport box second:
>>> print leftColumn().strip()
@@ -182,10 +185,9 @@
<div class="box">Patriots (23) : Steelers (7)</div>
</div>
-Now let's change the weight around ...
+Now let's change the order...
- >>> WeatherBox.weight = 1
- >>> SportBox.weight = 0
+ >>> shown.reverse()
and the order should switch as well:
@@ -195,10 +197,77 @@
<div class="box">It is sunny today!</div>
</div>
+Of course, we also can remove a shown viewlet:
+ >>> weather = shown.pop()
+ >>> print leftColumn().strip()
+ <div class="left-column">
+ <div class="box">Patriots (23) : Steelers (7)</div>
+ </div>
+
+
+Viewlet Base Classes
+--------------------
+
+
A Complex Example
-----------------
+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(object):
+ ... zope.interface.implements(IFile)
+ ... def __init__(self, data=''):
+ ... 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(dict):
+ ... pass
+
+Here is some sample data:
+
+ >>> container = Container()
+ >>> container['test.txt'] = File('Hello World!')
+ >>> container['mypage.html'] = File('<html><body>Hello World!</body></html>')
+ >>> container['data.xml'] = File('<message>Hello World!</message>')
+
+The contents view of the container should iterate through the container and
+represent the files in a table
+
+
+
+
+ >>> sortedBy = 'name', +1
+ >>> shownColumns = ['icon', 'name', 'size']
+
+
+
+
#
#Viewlet
#~~~~~~~
Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py 2005-10-09 11:17:02 UTC (rev 39004)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py 2005-10-09 12:01:02 UTC (rev 39005)
@@ -27,34 +27,34 @@
class IViewlet(IContentProvider):
"""A content provider that is managed by another content provider, known
as viewlet manager.
+
+ Note that you *cannot* call viewlets directly as a provider, i.e. through
+ the TALES ``provider`` expression, since it always has to know its manager.
"""
+ manager = zope.interface.Attribute(
+ """The Viewlet Manager
+ The viewlet manager for which the viewlet is registered. The viewlet
+ manager will contain any additional data that was provided by the
+ view, for example the TAL namespace attributes.
+ """)
+
+
class IViewletManager(IContentProvider,
zope.interface.common.mapping.IReadMapping):
- """An object that provides access to the content providers.
+ """A component that provides access to the content providers.
The viewlet manager's resposibilities are:
- (1) Aggregation of all viewlets of a given type.
+ (1) Aggregation of all viewlets registered for the manager.
(2) Apply a set of filters to determine the availability of the
viewlets.
(3) Sort the viewlets based on some implemented policy.
- """
- providerType = zope.interface.Attribute(
- '''The specific type of provider that are displayed by this manager.''')
+ (4) Provide an environment in which the viewlets are rendered.
-
-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 manager is supporting this
- sort mechanism."""),
- required=False,
- default=0)
+ (5) Render itself containing the HTML content of the viewlets.
+ """
Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py 2005-10-09 11:17:02 UTC (rev 39004)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py 2005-10-09 12:01:02 UTC (rev 39005)
@@ -32,8 +32,6 @@
"""
zope.interface.implements(interfaces.IViewletManager)
- providerType = None
-
def __init__(self, context, request, view):
self.context = context
self.request = request
@@ -42,52 +40,64 @@
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.providerType,
+ # Find the viewlet
+ viewlet = zope.component.queryMultiAdapter(
+ (self.context, self.request, self.view, self), interfaces.IViewlet,
name=name)
- # If the content provider was not found, then raise a lookup error
- if provider is None:
+ # 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 content provider cannot be accessed, then raise an
+ # If the viewlet cannot be accessed, then raise an
# unauthorized error
- if not zope.security.canAccess(provider, '__call__'):
+ if not zope.security.canAccess(viewlet, '__call__'):
raise zope.security.interfaces.Unauthorized(
'You are not authorized to access the provider '
'called `%s`.' %name)
- # Return the rendered content provider.
- return provider
+ # Return the rendered viewlet.
+ return viewlet
def get(self, name, default=None):
+ """See zope.interface.common.mapping.IReadMapping"""
try:
return self[name]
except (zope.component.interfaces.ComponentLookupError,
zope.security.interfaces.Unauthorized):
return default
+ 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
+ return [(name, viewlet) for name, viewlet in viewlets
+ if zope.security.canAccess(viewlet, '__call__')]
+
+ def sort(self, viewlets):
+ """Sort the viewlets.
+
+ ``viewlets`` is a list of tuples of the form (name, viewlet).
+ """
+ # By default, use the standard Python way of doing sorting.
+ return sorted(viewlets, lambda x, y: cmp(x[1], y[1]))
+
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.providerType)
+ (self.context, self.request, self.view, self), interfaces.IViewlet)
- # 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__')]
+ viewlets = self.filter(viewlets)
+ viewlets = self.sort(viewlets)
- # Sort the content providers by weight.
- if self.providerType.extends(interfaces.IWeightSupport):
- viewlets.sort(lambda x, y: cmp(x.weight, y.weight))
- else:
- viewlets.sort()
+ # Just use the viewlets from now on
+ viewlets = [viewlet for name, viewlet in viewlets]
# Now render the view
if self.template:
@@ -96,11 +106,14 @@
return u'\n'.join([viewlet() for viewlet in viewlets])
-def ViewletManager(providerType, template=None):
+def ViewletManager(interface, template=None, bases=()):
if template is not None:
template = ViewPageTemplateFile(template)
- return type('<ViewletManager for %s>' %providerType.getName(),
- (ViewletManagerBase,),
- {'providerType': providerType, 'template': template})
+ ViewletManager = type(
+ '<ViewletManager providing %s>' % interface.getName(),
+ bases+(ViewletManagerBase,),
+ {'template': template})
+ zope.interface.classImplements(ViewletManager, interface)
+ return ViewletManager
More information about the Zope3-Checkins
mailing list