[Zope-Checkins] SVN: Products.Five/trunk/ * Enabled the viewlet related directives by default.

Alec Mitchell cvs-admin at zope.org
Thu Jun 15 17:46:14 EDT 2006


Log message for revision 68671:
  * Enabled the viewlet related directives by default.
  
  * Added acquisition wrappers to viewlets before updating or rendering.
  
  * Moved the custom 'provider:' tales expression back into Five.  Made the provider directive acquisition wrap the resultant content provider so that simple providers that need security declarations (e.g. those that render pagetemplates) can work with the Zope 2 security machinery.
  

Changed:
  U   Products.Five/trunk/CHANGES.txt
  A   Products.Five/trunk/browser/providerexpression.py
  U   Products.Five/trunk/browser/tests/provider.txt
  U   Products.Five/trunk/browser/tests/provider.zcml
  A   Products.Five/trunk/browser/tests/provider_template_based.pt
  U   Products.Five/trunk/configure.zcml
  U   Products.Five/trunk/viewlet/configure.zcml
  U   Products.Five/trunk/viewlet/directives.txt
  U   Products.Five/trunk/viewlet/manager.py
  U   Products.Five/trunk/viewlet/tests.py

-=-
Modified: Products.Five/trunk/CHANGES.txt
===================================================================
--- Products.Five/trunk/CHANGES.txt	2006-06-15 18:19:07 UTC (rev 68670)
+++ Products.Five/trunk/CHANGES.txt	2006-06-15 21:46:10 UTC (rev 68671)
@@ -5,6 +5,15 @@
 Five 1.5 (unreleased)
 =====================
 
+* Enabled the viewlet related directives by default.
+
+* Added acquisition wrappers to viewlets before updating or rendering.
+
+* Moved the custom 'provider:' tales expression back into Five.  Made the
+  provider directive acquisition wrap the resultant content provider so that
+  simple providers that need security declarations (e.g. those that render
+  pagetemplates) can work with the Zope 2 security machinery.
+
 * Added Five.browser.pagetemplatefile.ViewPageTemplateFile as an alias
   to ZopeTwoPageTemplateFile and as a Zope 2 correspondence to
   zope.app.pagetemplate.ViewPageTemplateFile.
@@ -17,11 +26,11 @@
 
 * Zope 2.10+ now includes site.zcml as part of its instance creation
   skel directory.  As a consequence Five now requires this file to exist
-  in every instance.  If upgrading a site from Zope 2.9 to 2.10, you will 
-  need to copy site.zcml and package-includes/ from your installed Zope 
-  installation location (skel/etc/) into the etc/ directory of your upgraded 
+  in every instance.  If upgrading a site from Zope 2.9 to 2.10, you will
+  need to copy site.zcml and package-includes/ from your installed Zope
+  installation location (skel/etc/) into the etc/ directory of your upgraded
   instance.
-  
+
   The rationale for requiring this new file is to bring Zope 2 instances
   closer in consistency to Zope 3 instances.  It also eases use of Zope 3
   coding techniques in Zope 2 and removes some confusion when trying
@@ -190,7 +199,7 @@
   When no Zope 3-style view is found, first the object's original
   ``__bobo_traverse__`` is tried.  If that does not exist, Traversable
   resorts to attribute look-up.
-  
+
 * Unit tests that did i18n via Localizer would fail because the
   request attribute that keeps Localizers list of preferred languages
   did not exist.

Added: Products.Five/trunk/browser/providerexpression.py
===================================================================
--- Products.Five/trunk/browser/providerexpression.py	2006-06-15 18:19:07 UTC (rev 68670)
+++ Products.Five/trunk/browser/providerexpression.py	2006-06-15 21:46:10 UTC (rev 68671)
@@ -0,0 +1,37 @@
+import zope.component
+from zope.contentprovider import interfaces as cp_interfaces
+from zope.contentprovider.tales import addTALNamespaceData
+from zope.interface import implements
+from zope.tales.expressions import StringExpr
+
+class Z2ProviderExpression(StringExpr):
+    """Create a custom provider expression which overrides __call__ to
+       acquisition wrap the provider so that security lookups can be done."""
+
+    implements(cp_interfaces.ITALESProviderExpression)
+
+    def __call__(self, econtext):
+        name = super(Z2ProviderExpression, self).__call__(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), cp_interfaces.IContentProvider, name)
+
+        # Provide a useful error message, if the provider was not found.
+        if provider is None:
+            raise cp_interfaces.ContentProviderLookupError(name)
+
+        if getattr(provider, '__of__', None) is not None:
+            provider = provider.__of__(context)
+
+        # 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()

Modified: Products.Five/trunk/browser/tests/provider.txt
===================================================================
--- Products.Five/trunk/browser/tests/provider.txt	2006-06-15 18:19:07 UTC (rev 68670)
+++ Products.Five/trunk/browser/tests/provider.txt	2006-06-15 21:46:10 UTC (rev 68671)
@@ -187,3 +187,45 @@
       </div>
     </body>
   </html>
+
+Now we test a provider using a PageTemplateFile to render itself.  It must
+inherit from an Acquisition base class so that the template can use Zope 2
+security mechanisms:
+
+  >>> import os, tempfile
+  >>> temp_dir = tempfile.mkdtemp()
+  >>> dynTemplate = os.path.join(temp_dir, 'dynamic_template.pt')
+  >>> open(dynTemplate, 'w').write(
+  ...   'A simple template: <tal:simple replace="python:view.my_property" />')
+  >>> from Acquisition import Explicit
+  >>> from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
+  >>> class TemplateProvider(Explicit):
+  ...     zope.component.adapts(zope.interface.Interface,
+  ...                           browser.IDefaultBrowserLayer,
+  ...                           zope.interface.Interface)
+  ...
+  ...     def __init__(self, context, request, view):
+  ...         self.__parent__ = view
+  ...         self.context = context
+  ...         self.request = request
+  ...         self.view = view
+  ...
+  ...     def update(self):
+  ...         pass
+  ...     # Is there a better way to tell it to look in the current dir?
+  ...     render = ZopeTwoPageTemplateFile(dynTemplate, temp_dir)
+  ...     my_property = 'A string for you'
+
+  >>> zope.component.provideAdapter(TemplateProvider, name='mypage.TemplateProvider', provides=interfaces.IContentProvider)
+  >>> print http(r'''
+  ... GET /test_folder_1_/content_obj/template_based.html HTTP/1.1
+  ... ''')
+  HTTP/1.1 200 OK
+  ...
+  A simple template: A string for you
+
+ Cleanup
+ -------
+
+  >>> import shutil
+  >>> shutil.rmtree(temp_dir)

Modified: Products.Five/trunk/browser/tests/provider.zcml
===================================================================
--- Products.Five/trunk/browser/tests/provider.zcml	2006-06-15 18:19:07 UTC (rev 68670)
+++ Products.Five/trunk/browser/tests/provider.zcml	2006-06-15 21:46:10 UTC (rev 68671)
@@ -29,5 +29,11 @@
       name="namespace2.html"
       permission="zope2.Public"
       />
+  <browser:page
+      for="Products.Five.tests.testing.simplecontent.ISimpleContent"
+      template="provider_template_based.pt"
+      name="template_based.html"
+      permission="zope2.View"
+      />
 
 </configure>

Added: Products.Five/trunk/browser/tests/provider_template_based.pt
===================================================================
--- Products.Five/trunk/browser/tests/provider_template_based.pt	2006-06-15 18:19:07 UTC (rev 68670)
+++ Products.Five/trunk/browser/tests/provider_template_based.pt	2006-06-15 21:46:10 UTC (rev 68671)
@@ -0,0 +1 @@
+<tal:block replace="structure provider:mypage.TemplateProvider" />

Modified: Products.Five/trunk/configure.zcml
===================================================================
--- Products.Five/trunk/configure.zcml	2006-06-15 18:19:07 UTC (rev 68670)
+++ Products.Five/trunk/configure.zcml	2006-06-15 21:46:10 UTC (rev 68671)
@@ -14,6 +14,7 @@
   <include package=".formlib" />
   <include package=".skin" />
   <include package=".utilities" />
+  <include package=".viewlet" />
 
   <!-- this is really lying, but it's to please checkContainer -->
   <five:implements class="OFS.ObjectManager.ObjectManager"

Modified: Products.Five/trunk/viewlet/configure.zcml
===================================================================
--- Products.Five/trunk/viewlet/configure.zcml	2006-06-15 18:19:07 UTC (rev 68670)
+++ Products.Five/trunk/viewlet/configure.zcml	2006-06-15 21:46:10 UTC (rev 68671)
@@ -1,6 +1,8 @@
 <configure xmlns="http://namespaces.zope.org/zope"
            xmlns:browser="http://namespaces.zope.org/browser">
 
+  <include file="meta.zcml" />
+
   <interface
       interface="zope.viewlet.interfaces.IViewletManager"
       />

Modified: Products.Five/trunk/viewlet/directives.txt
===================================================================
--- Products.Five/trunk/viewlet/directives.txt	2006-06-15 18:19:07 UTC (rev 68670)
+++ Products.Five/trunk/viewlet/directives.txt	2006-06-15 21:46:10 UTC (rev 68671)
@@ -400,7 +400,7 @@
   ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
   ...   <viewletManager
   ...       name="newcolumn"
-  ...       permission="zope.Public"
+  ...       permission="zope2.View"
   ...       provides="Products.Five.viewlet.tests.INewColumn"
   ...       />
   ... </configure>
@@ -443,7 +443,6 @@
   ...       manager="Products.Five.viewlet.tests.INewColumn"
   ...       template="%s"
   ...       permission="zope.Public"
-  ...       extra_string_attributes="can be specified"
   ...       />
   ... </configure>
   ... ''' % weatherTemplate)
@@ -487,6 +486,45 @@
        </div>
   ...
 
+A Dynamic Viewlet
+-----------------
+
+A viewlet template can of course contain some dynamic code, let's see how
+that works:
+
+  >>> dynWeatherTemplate = os.path.join(temp_dir, 'dynamic_weather.pt')
+  >>> open(dynWeatherTemplate, 'w').write(u'''
+  ... <div tal:define="city view/city;"><span tal:replace="string:${city/name}: ${city/temp} F" /></div>'''
+  ... )
+
+  >>> context = zcml.load_string('''
+  ... <configure xmlns="http://namespaces.zope.org/browser" i18n_domain="zope">
+  ...   <viewlet
+  ...       name="dynweather"
+  ...       for="*"
+  ...       manager="Products.Five.viewlet.tests.INewColumn"
+  ...       class="Products.Five.viewlet.tests.DynamicTempBox"
+  ...       template="%s"
+  ...       permission="zope2.View"
+  ...       />
+  ... </configure>
+  ... ''' % dynWeatherTemplate)
+
+Now we request the view to ensure that we can see the dynamic template
+rendered as expected:
+
+  >>> 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>Los Angeles, CA: 78 F</div>
+       <div>sunny</div>
+       </div>
+  ...
+
 Cleanup
 -------
 

Modified: Products.Five/trunk/viewlet/manager.py
===================================================================
--- Products.Five/trunk/viewlet/manager.py	2006-06-15 18:19:07 UTC (rev 68670)
+++ Products.Five/trunk/viewlet/manager.py	2006-06-15 21:46:10 UTC (rev 68671)
@@ -7,6 +7,7 @@
 
 from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
 
+aq_base = Acquisition.aq_base
 
 class ViewletManagerBase(origManagerBase, Acquisition.Explicit):
     """A base class for Viewlet managers to work in Zope2"""
@@ -23,9 +24,12 @@
             raise zope.component.interfaces.ComponentLookupError(
                 'No provider with name `%s` found.' %name)
 
+        # Wrap the viewlet for security lookups
+        viewlet = viewlet.__of__(viewlet.context)
+
         # If the viewlet cannot be accessed, then raise an
         # unauthorized error
-        if not guarded_hasattr(viewlet.__of__(viewlet.context), 'render'):
+        if not guarded_hasattr(viewlet, 'render'):
             raise zope.security.interfaces.Unauthorized(
                 'You are not authorized to access the provider '
                 'called `%s`.' %name)
@@ -38,13 +42,27 @@
 
         ``viewlets`` is a list of tuples of the form (name, viewlet).
         """
+        results = []
         # 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')]
+        for name, viewlet in viewlets:
+            viewlet = viewlet.__of__(viewlet.context)
+            if guarded_hasattr(viewlet, 'render'):
+                results.append((name, viewlet))
+        return results
 
+    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. Unwrap the
+        # objects first so that they are sorted as expected.  This is dumb
+        # but it allows the tests to have deterministic results.
+        return sorted(viewlets, lambda x, y: cmp(aq_base(x[1]), aq_base(y[1])))
+
 def ViewletManager(name, interface, template=None, bases=()):
 
     if template is not None:

Modified: Products.Five/trunk/viewlet/tests.py
===================================================================
--- Products.Five/trunk/viewlet/tests.py	2006-06-15 18:19:07 UTC (rev 68670)
+++ Products.Five/trunk/viewlet/tests.py	2006-06-15 21:46:10 UTC (rev 68671)
@@ -73,6 +73,10 @@
     def __call__(self):
         return u'Red Sox vs. White Sox'
 
+class DynamicTempBox(object):
+    weight = 0
+    city = {'name': 'Los Angeles, CA', 'temp': 78}
+
 def setUp(test):
     setup.placefulSetUp()
 



More information about the Zope-Checkins mailing list