[Checkins] SVN: z3c.jbot/trunk/ Added support for per-layer overrides.

Malthe Borch mborch at gmail.com
Mon Jul 14 13:33:54 EDT 2008


Log message for revision 88363:
  Added support for per-layer overrides.

Changed:
  U   z3c.jbot/trunk/README.txt
  U   z3c.jbot/trunk/setup.py
  U   z3c.jbot/trunk/z3c/jbot/Five.txt
  U   z3c.jbot/trunk/z3c/jbot/README.txt
  U   z3c.jbot/trunk/z3c/jbot/__init__.py
  A   z3c.jbot/trunk/z3c/jbot/interfaces.py
  U   z3c.jbot/trunk/z3c/jbot/manager.py
  U   z3c.jbot/trunk/z3c/jbot/metaconfigure.py
  U   z3c.jbot/trunk/z3c/jbot/metadirectives.py
  U   z3c.jbot/trunk/z3c/jbot/tests/test_doctests.py
  A   z3c.jbot/trunk/z3c/jbot/utility.py

-=-
Modified: z3c.jbot/trunk/README.txt
===================================================================
--- z3c.jbot/trunk/README.txt	2008-07-14 17:23:41 UTC (rev 88362)
+++ z3c.jbot/trunk/README.txt	2008-07-14 17:33:53 UTC (rev 88363)
@@ -9,6 +9,9 @@
 template overrides directory and give your new template the canonical
 filename.
 
+Overrides may be registered for a specific request-layer.
+
+
 Canonical filename
 ------------------
 
@@ -21,16 +24,25 @@
   Suppose you want to override: /plone/app/layout/viewlets/logo.pt
   You would use the filename:   plone.app.layout.viewlets.logo.pt
 
+
 Registering a on overrides directory
 ------------------------------------
 
 In python:
 
-  >>> from z3c.jbot.manager import getGlobalTemplateManager
-  >>> getGlobalTemplateManager().registerDirectory(directory)
+  >>> from z3c.jbot.utility import getManager
+  >>> getManager().registerDirectory(directory)
 
-In ZCML:
+Using ZCML:
 
   <include package="z3c.jbot" file="meta.zcml" />
-  <browser:templateOverrides directory="<directory>" />
   
+  <browser:templateOverrides
+      directory="<directory>"
+      layer="<layer>" />
+  
+
+Author
+------
+
+Malthe Borch <mborch at gmail.com>

Modified: z3c.jbot/trunk/setup.py
===================================================================
--- z3c.jbot/trunk/setup.py	2008-07-14 17:23:41 UTC (rev 88362)
+++ z3c.jbot/trunk/setup.py	2008-07-14 17:33:53 UTC (rev 88363)
@@ -1,7 +1,7 @@
 from setuptools import setup, find_packages
 import sys, os
 
-version = '0.1.6dev'
+version = '0.2dev'
 
 setup(name='z3c.jbot',
       version=version,
@@ -27,6 +27,9 @@
           'zope.pagetemplate',
           'zope.component',
           'zope.configuration',
+          'zope.security',
+          'zope.publisher',
+          'zope.app.component',
           # -*- Extra requirements: -*-
       ],
       entry_points="""

Modified: z3c.jbot/trunk/z3c/jbot/Five.txt
===================================================================
--- z3c.jbot/trunk/z3c/jbot/Five.txt	2008-07-14 17:23:41 UTC (rev 88362)
+++ z3c.jbot/trunk/z3c/jbot/Five.txt	2008-07-14 17:33:53 UTC (rev 88363)
@@ -28,10 +28,52 @@
 Register template overrides directory.
   
   >>> import z3c.jbot.manager
-  >>> manager = z3c.jbot.manager.getGlobalTemplateManager()
+  >>> import z3c.jbot.interfaces
+  
+  >>> factory = z3c.jbot.manager.TemplateManagerFactory()
+  >>> component.provideAdapter(
+  ...     factory, (interface.Interface,), z3c.jbot.interfaces.ITemplateManager)
+
+  >>> manager = factory.manager
   >>> manager.registerDirectory(directory)
 
 Verify that the override is rendered.
   
   >>> view.template()
   u'This template will override the example template.\n'
+
+  >>> manager.unregisterDirectory(directory)
+
+Register override for HTTP-request layer.
+
+  >>> from zope.publisher.interfaces.browser import IHTTPRequest
+  >>> factory = z3c.jbot.manager.TemplateManagerFactory()
+  >>> component.provideAdapter(
+  ...     factory, (IHTTPRequest,), z3c.jbot.interfaces.ITemplateManager)
+
+  >>> manager = factory.manager
+  >>> manager.registerDirectory(directory)
+  
+  >>> view.template()
+  u'This is an example page template.\n'
+
+On Zope 2, the request is acquired from the site as set by the
+component site hook.
+  
+  >>> from zope.publisher.browser import TestRequest
+  >>> class MockSite(object):
+  ...     REQUEST = TestRequest()
+  ...     getSiteManager = component.getSiteManager
+  
+  >>> from zope.app.component.hooks import setHooks, setSite
+  >>> setHooks()
+  >>> setSite(MockSite())
+
+Examine the request.
+  
+  >>> import z3c.jbot.utility
+  >>> z3c.jbot.utility.getRequest()
+  <zope.publisher.browser.TestRequest ...>
+  
+  >>> z3c.jbot.utility.getManager() is manager
+  True

Modified: z3c.jbot/trunk/z3c/jbot/README.txt
===================================================================
--- z3c.jbot/trunk/z3c/jbot/README.txt	2008-07-14 17:23:41 UTC (rev 88362)
+++ z3c.jbot/trunk/z3c/jbot/README.txt	2008-07-14 17:33:53 UTC (rev 88363)
@@ -30,8 +30,18 @@
   >>> import z3c.jbot.tests
   >>> directory = z3c.jbot.tests.__path__[0]
 
+Register template manager factory. We'll register it for
+``zope.interface.Interface`` which makes it available on all layers.
+  
   >>> import z3c.jbot.manager
-  >>> manager = z3c.jbot.manager.getGlobalTemplateManager()
+  >>> import z3c.jbot.interfaces
+  >>> factory = z3c.jbot.manager.TemplateManagerFactory()
+  >>> component.provideAdapter(
+  ...     factory, (interface.Interface,), z3c.jbot.interfaces.ITemplateManager)
+
+Register overrides directory.
+  
+  >>> manager = factory.manager
   >>> manager.registerDirectory("%s/templates" % directory)
 
 Verify that we've registered the contents of the directory:
@@ -63,6 +73,71 @@
   >>> template.filename
   '.../z3c.jbot/z3c/jbot/tests/templates/example.pt'
 
+Overrides can be registered for a specific layer. Let's re-register an
+override template factory for the HTTP-request layer.
+
+  >>> from zope.publisher.interfaces.browser import IHTTPRequest
+  >>> factory = z3c.jbot.manager.TemplateManagerFactory()
+  >>> component.provideAdapter(
+  ...     factory, (IHTTPRequest,), z3c.jbot.interfaces.ITemplateManager)
+
+Register overrides directory.
+  
+  >>> manager = factory.manager
+  >>> manager.registerDirectory("%s/templates" % directory)
+
+Let's set up an interaction with a base request.
+
+  >>> import zope.security.management
+  >>> import zope.publisher.base
+  >>> request = zope.publisher.base.BaseRequest("", {})
+  >>> IHTTPRequest.providedBy(request)
+  False
+  >>> zope.security.management.newInteraction(request)  
+
+Since this request is not an HTTP-request, we don't expect the
+override to be enabled.
+
+  >>> template()
+  u'This is an example page template.\n'
+
+Let's now engage in an interaction with an HTTP-request.
+  
+  >>> interface.alsoProvides(request, IHTTPRequest)
+  >>> template()
+  u'This template will override the example template.\n'
+
+  >>> template._v_cooked
+  1
+  
+Going back to a basic request.
+
+  >>> interface.noLongerProvides(request, IHTTPRequest)
+  >>> IHTTPRequest.providedBy(request)
+  False
+  
+  >>> template()
+  u'This is an example page template.\n'
+
+Let's verify that we only cook once per template source.
+
+  >>> import z3c.jbot.utility
+  >>> z3c.jbot.utility.getManager().registerTemplate(template)
+  >>> template._v_last_read and template._v_cooked
+  1
+
+  >>> interface.alsoProvides(request, IHTTPRequest)
+  >>> z3c.jbot.utility.getManager().registerTemplate(template)
+  >>> template._v_last_read and template._v_cooked
+  1
+
+  >>> template()
+  u'This template will override the example template.\n'
+
+  >>> z3c.jbot.utility.getManager().unregisterDirectory("%s/templates" % directory)
+  >>> interface.noLongerProvides(request, IHTTPRequest)
+  >>> z3c.jbot.utility.getManager().unregisterDirectory("%s/templates" % directory)
+  
 Configuring template override directories in ZCML
 -------------------------------------------------
 
@@ -86,4 +161,23 @@
   >>> template()
   u'This template will override the example template.\n'
 
-  >>> manager.unregisterDirectory("%s/templates" % directory)
+  >>> z3c.jbot.utility.getManager().unregisterDirectory("%s/templates" % directory)
+
+Let's register overrides for the HTTP-request layer.
+
+  >>> xmlconfig.xmlconfig(StringIO("""
+  ... <configure xmlns="http://namespaces.zope.org/browser">
+  ... <templateOverrides
+  ...      directory="%s/templates"
+  ...      layer="zope.publisher.interfaces.browser.IHTTPRequest" />
+  ... </configure>
+  ... """ % directory))
+
+  >>> template()
+  u'This is an example page template.\n'
+
+If we now provide the HTTP-request layer, the override becomes active.
+  
+  >>> interface.alsoProvides(request, IHTTPRequest)
+  >>> template()
+  u'This template will override the example template.\n'

Modified: z3c.jbot/trunk/z3c/jbot/__init__.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/__init__.py	2008-07-14 17:23:41 UTC (rev 88362)
+++ z3c.jbot/trunk/z3c/jbot/__init__.py	2008-07-14 17:33:53 UTC (rev 88363)
@@ -1,13 +1,48 @@
-import manager
 from zope.pagetemplate.pagetemplatefile import PageTemplateFile
 
-# add registration hook to ``zope.app.pagetemplate``
+import manager
+import utility
+
+NO_DEFAULT = object()
+
+class LayerProperty(property):
+    """Layer-specific property class.
+
+    Instance attributes are instance *and* layer-specific when defined
+    using this property class.
+    """
+    
+    def __init__(self, name):
+        self.name = name
+        self.default = getattr(PageTemplateFile, name, NO_DEFAULT)
+        property.__init__(self, self._get, self._set)
+        
+    def _get(self, template):
+        layer = utility.getLayer()
+        attributes = getattr(template, '_v_attrs', template.__dict__)
+        key = self.name
+        if (layer, key) in attributes:
+            return attributes[layer, key]
+
+        return self.default
+    
+    def _set(self, template, value):
+        layer = utility.getLayer()
+        template.__dict__.setdefault('_v_attrs', {})[layer, self.name] = value
+        if self.default is NO_DEFAULT:
+            self.default = value
+            
+# registration hook to template manager
 def jbot(func):
-    def jbot_func(self, *args, **kwargs): 
-        manager.getGlobalTemplateManager().registerTemplate(self)
+    def patch(self, *args, **kwargs):
+        manager = utility.getManager()
+        if manager is not None:
+            manager.registerTemplate(self)
+        
         return func(self, *args, **kwargs)        
-    return jbot_func
+    return patch
 
+# patch ``_cook_check``-method to insert jbot-logic
 PageTemplateFile._cook_check = jbot(PageTemplateFile._cook_check)
 try:
     from Products.PageTemplates.PageTemplateFile import PageTemplateFile as Z2PageTemplateFile
@@ -15,4 +50,7 @@
 except ImportError:
     pass
 
-
+# munge per-layer attribute descriptors on class
+for name in ('_v_macros', '_v_program', '_v_cooked', '_v_errors',
+             '_v_last_read', '_v_warning', '_text_', 'filename'):
+    setattr(PageTemplateFile, name, LayerProperty(name))

Added: z3c.jbot/trunk/z3c/jbot/interfaces.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/interfaces.py	                        (rev 0)
+++ z3c.jbot/trunk/z3c/jbot/interfaces.py	2008-07-14 17:33:53 UTC (rev 88363)
@@ -0,0 +1,10 @@
+from zope import interface
+
+class ITemplateManager(interface.Interface):
+    def registerDirectory(directory):
+        pass
+
+    def unregisterDirectory(directory):
+        pass
+
+    

Modified: z3c.jbot/trunk/z3c/jbot/manager.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/manager.py	2008-07-14 17:23:41 UTC (rev 88362)
+++ z3c.jbot/trunk/z3c/jbot/manager.py	2008-07-14 17:33:53 UTC (rev 88363)
@@ -1,6 +1,10 @@
+from zope import interface
+
 import sys
 import os.path
 
+import interfaces
+
 IGNORE = object()
 
 def root_length(a, b):
@@ -29,62 +33,86 @@
         path = path[1:]
         
     return path
+
+class TemplateManagerFactory(object):
+    def __init__(self):
+        self.manager = TemplateManager()
+
+    def __call__(self, layer):
+        return self.manager
     
-class GlobalTemplateManager(object):
+class TemplateManager(object):
+    interface.implements(interfaces.ITemplateManager)
+    
     def __init__(self):
         self.syspaths = tuple(sys.path)
         self.templates = {}
         self.paths = {}
-        
+
     def registerDirectory(self, directory):
         for filename in os.listdir(directory):
             if filename.endswith('.pt'):
                 self.paths[filename] = "%s/%s" % (directory, filename)
 
+        for template, filename in self.templates.items():
+            if filename is IGNORE:
+                del self.templates[template]
+
     def unregisterDirectory(self, directory):
+        templates = []
+        
+        for template, filename in self.templates.items():
+            if filename in self.paths:
+                templates.append(template)
+
         for filename in os.listdir(directory):
             if filename in self.paths:
                 del self.paths[filename]
+
+        for template in templates:
+            template._v_last_read = False
         
     def registerTemplate(self, template):
         # only register templates that have a filename attribute
         if not hasattr(template, 'filename'):
             return
         
-        # assert that template is not already registered
+        # assert that the template is not already registered
         filename = self.templates.get(template)
         if filename is IGNORE:
             return
 
-        if self.paths.get(filename) == template.filename:
+        # if the template filename matches an override, we're done
+        paths = self.paths
+        if paths.get(filename) == template.filename:
             return
 
-        if filename is not None and filename not in self.paths:
-            # template file has been unregistered; restore
-            # original template
+        # verify that override has not been unregistered
+        if filename is not None and filename not in paths:
+            # restore original template
             template.filename = template._filename
             delattr(template, '_filename')
-
+            del self.templates[template]
+            
+        # check if an override exists
         path = find_package(self.syspaths, template.filename)
         if path is None:
+            # permanently ignore template
             self.templates[template] = IGNORE
             return
         
         filename = path.replace(os.path.sep, '.')
+        if filename in paths:
+            path = paths[filename]
 
-        if filename in self.paths:
-            path = self.paths[filename]
-            
             # save original filename
             template._filename = template.filename
 
             # save template and registry and assign path
             template.filename = path
             self.templates[template] = filename
+        else:
+            self.templates[template] = IGNORE
 
+        # force cook
         template._v_last_read = False
-        
-GlobalTemplateManager = GlobalTemplateManager()
-
-def getGlobalTemplateManager():
-    return GlobalTemplateManager

Modified: z3c.jbot/trunk/z3c/jbot/metaconfigure.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/metaconfigure.py	2008-07-14 17:23:41 UTC (rev 88362)
+++ z3c.jbot/trunk/z3c/jbot/metaconfigure.py	2008-07-14 17:33:53 UTC (rev 88363)
@@ -1,5 +1,24 @@
+from zope import interface
 from zope import component
-from z3c.jbot.manager import getGlobalTemplateManager
 
-def templateOverridesDirective(_context, directory):
-    getGlobalTemplateManager().registerDirectory(directory)
+import manager
+import interfaces
+
+def handler(directory, layer):
+    gsm = component.getGlobalSiteManager()
+
+    # check if a template manager already exists
+    factory = gsm.adapters.lookup((layer,), interfaces.ITemplateManager)
+
+    if factory is None:
+        factory = TemplateManagerFactory()
+        component.provideAdapter(factory, (layer,), interfaces.ITemplateManager)
+
+    factory.manager.registerDirectory(directory)
+
+def templateOverridesDirective(_context, directory, layer=interface.Interface):
+    _context.action(
+        discriminator = ('override', directory, layer),
+        callable = handler,
+        args = (directory, layer),
+        )

Modified: z3c.jbot/trunk/z3c/jbot/metadirectives.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/metadirectives.py	2008-07-14 17:23:41 UTC (rev 88362)
+++ z3c.jbot/trunk/z3c/jbot/metadirectives.py	2008-07-14 17:33:53 UTC (rev 88363)
@@ -1,5 +1,6 @@
 from zope.interface import Interface
 from zope.configuration import fields
+from zope.app.component.back35 import LayerField
 
 class ITemplateOverridesDirective(Interface):
     """Directive which registers a directory with template overrides."""
@@ -8,3 +9,9 @@
         title=u"Path to directory",
         required=True)
 
+    layer = LayerField(
+        title=u"The layer the overrides should be enabled for",
+        description=u"By default overrides are used for all layers.",
+        required=False
+        )
+

Modified: z3c.jbot/trunk/z3c/jbot/tests/test_doctests.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/tests/test_doctests.py	2008-07-14 17:23:41 UTC (rev 88362)
+++ z3c.jbot/trunk/z3c/jbot/tests/test_doctests.py	2008-07-14 17:33:53 UTC (rev 88363)
@@ -1,15 +1,19 @@
+import zope.interface
+import zope.component
 import zope.testing
 import unittest
 
-OPTIONFLAGS = (zope.testing.doctest.REPORT_ONLY_FIRST_FAILURE |
-               zope.testing.doctest.ELLIPSIS |
+OPTIONFLAGS = (zope.testing.doctest.ELLIPSIS |
                zope.testing.doctest.NORMALIZE_WHITESPACE)
 
 import zope.component.testing
 
 def test_suite():
     doctests = ['README.txt']
-
+    globs = dict(
+        interface=zope.interface,
+        component=zope.component)
+    
     try:
         import Products.Five
         doctests.append('Five.txt')
@@ -21,6 +25,7 @@
                                           optionflags=OPTIONFLAGS,
                                           setUp=zope.component.testing.setUp,
                                           tearDown=zope.component.testing.tearDown,
+                                          globs=globs,
                                           package="z3c.jbot") for doctest in doctests
         ))
 

Added: z3c.jbot/trunk/z3c/jbot/utility.py
===================================================================
--- z3c.jbot/trunk/z3c/jbot/utility.py	                        (rev 0)
+++ z3c.jbot/trunk/z3c/jbot/utility.py	2008-07-14 17:33:53 UTC (rev 88363)
@@ -0,0 +1,47 @@
+from zope import interface
+from zope import component
+
+from zope.app.component.hooks import getSite
+from zope.publisher.interfaces import IRequest
+
+import zope.security.management
+import zope.security.interfaces
+
+import interfaces
+
+try:
+    import Products.PageTemplates
+    ZOPE_2 = True
+except ImportError:
+    ZOPE_2 = False
+
+def getRequest():
+    try:
+        i = zope.security.management.getInteraction()
+    except zope.security.interfaces.NoInteraction:
+        if ZOPE_2:
+            # get request by acquisition
+            site = getSite()
+            if site is not None:
+                return site.REQUEST
+        return
+
+    for p in i.participations:
+        if IRequest.providedBy(p):
+            return p
+
+def getLayer():
+    request = getRequest()
+
+    if request is not None:
+        return interface.providedBy(request)
+
+    return interface.Interface
+
+def getManager():
+    layer = getLayer()
+    gsm = component.getGlobalSiteManager()
+        
+    factory = gsm.adapters.lookup((layer,), interfaces.ITemplateManager)
+    if factory is not None:
+        return factory.manager



More information about the Checkins mailing list