[Checkins] SVN: five.customerize/trunk/src/five/customerize/ Add the customerize ui and tests, currently we create the customized templates in the nearest local site

Alec Mitchell apm13 at columbia.edu
Sun Oct 29 15:14:44 EST 2006


Log message for revision 70975:
  Add the customerize ui and tests, currently we create the customized templates in the nearest local site
  

Changed:
  A   five.customerize/trunk/src/five/customerize/browser.py
  U   five.customerize/trunk/src/five/customerize/configure.zcml
  A   five.customerize/trunk/src/five/customerize/customerize.txt
  A   five.customerize/trunk/src/five/customerize/customize.pt
  U   five.customerize/trunk/src/five/customerize/tests.py
  U   five.customerize/trunk/src/five/customerize/zpt.py
  A   five.customerize/trunk/src/five/customerize/zptviews.pt

-=-
Added: five.customerize/trunk/src/five/customerize/browser.py
===================================================================
--- five.customerize/trunk/src/five/customerize/browser.py	2006-10-29 20:10:14 UTC (rev 70974)
+++ five.customerize/trunk/src/five/customerize/browser.py	2006-10-29 20:14:44 UTC (rev 70975)
@@ -0,0 +1,143 @@
+import os.path
+from Acquisition import aq_inner
+
+from Products.Five.component.interfaces import IObjectManagerSite
+from Products.Five.component import findSite
+from Products.Five.browser import BrowserView
+
+import zope.interface
+import zope.component
+import zope.dottedname.resolve
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.traversing.browser import absoluteURL
+from zope.app.apidoc.presentation import getViews
+
+from five.customerize.zpt import TTWTemplate
+
+def mangleAbsoluteFilename(filename):
+    """
+    Mangle an absolute filename when the file happens to be in a
+    package.  The mangled name will then be of the form::
+
+      <dotted package name>/<base filename>.
+
+    For example, let's take Five's configure.zcml as an example.  We
+    assemble it in an OS-independent way so this test works on all
+    platforms:
+    
+      >>> def filesystemPath(*elements):
+      ...     return os.path.sep.join(elements)
+
+    We see that the filename is now mangled:
+
+      >>> mangleAbsoluteFilename(filesystemPath(
+      ...     '', 'path', 'to', 'Products', 'Five', 'configure.zcml'))
+      'Products.Five/configure.zcml'
+
+    The name of a file that's not in a package is returned unchanged:
+
+      >>> not_in_a_package = filesystemPath('', 'path', 'to', 'configure.zcml')
+      >>> mangleAbsoluteFilename(not_in_a_package) == not_in_a_package
+      True
+    """
+    if not os.path.isabs(filename):
+        raise ValueError("Can only determine package for absolute filenames")
+    dir, basename = os.path.split(filename)
+    pieces = dir.split(os.path.sep)
+    if pieces[0] == '':
+        pieces = pieces[1:]
+    while pieces:
+        try:
+            zope.dottedname.resolve.resolve('.'.join(pieces))
+            break
+        except ImportError:
+            pieces = pieces[1:]
+    if not pieces:
+        return filename
+    return '.'.join(pieces) + '/' + basename
+
+class CustomizationView(BrowserView):
+
+    def templateViewRegistrations(self):
+        for reg in getViews(zope.interface.providedBy(self.context),
+                            IBrowserRequest):
+            factory = reg.factory
+            while hasattr(factory, 'factory'):
+                factory = factory.factory
+            #XXX this should really be dealt with using a marker interface
+            # on the view factory
+            if hasattr(factory, '__name__') and \
+                   factory.__name__.startswith('SimpleViewClass'):
+                yield reg
+
+    def templateViewRegInfo(self):
+        def regkey(reg):
+            return reg.name
+        for reg in sorted(self.templateViewRegistrations(), key=regkey):
+            yield {
+                'viewname': reg.name,
+                'for': reg.required[0].__identifier__,
+                'type': reg.required[1].__identifier__,
+                'zptfile': mangleAbsoluteFilename(reg.factory.index.filename),
+                'zcmlfile': mangleAbsoluteFilename(reg.info.file)
+                }
+    def viewClassFromViewName(self, viewname):
+        view = zope.component.getMultiAdapter((self.context, self.request),
+                                              name=viewname)
+        # The view class is generally auto-generated, we usually want
+        # the first base class, though if the view only has one base
+        # (generally object or BrowserView) we return the full class
+        # and hope that it can be pickled
+        klass = view.__class__
+        bases =klass.__bases__
+        if len(bases) == 1:
+            return klass
+        return bases[0]
+
+
+    def templateFromViewName(self, viewname):
+        view = zope.component.getMultiAdapter((self.context, self.request),
+                                              name=viewname)
+        return view.index
+
+    def templateCodeFromViewName(self, viewname):
+        template = self.templateFromViewName(viewname)
+        return open(template.filename, 'rb').read() #XXX: bad zope
+
+    def doCustomizeTemplate(self, viewname):
+        # find the nearest site
+        site = findSite(self.context, IObjectManagerSite)
+        if site is None:
+            raise TypeError("No site found")  #TODO find right exception
+
+        # we're using the original filename of the template, not the
+        # view name to avoid potential conflicts and/or confusion in
+        # URLs
+        template = self.templateFromViewName(viewname)
+        zpt_id = os.path.basename(template.filename)
+
+        template_file = self.templateCodeFromViewName(viewname)
+        viewclass = self.viewClassFromViewName(viewname)
+        viewzpt = TTWTemplate(zpt_id, template_file, view=viewclass)
+        site._setObject(zpt_id, viewzpt) #XXXthere could be a naming conflict
+        components = site.getSiteManager()
+
+        # find out the view registration object so we can get at the
+        # provided and required interfaces
+        for reg in getViews(zope.interface.providedBy(self.context),
+                            IBrowserRequest):
+            if reg.name == viewname:
+                break
+
+        components.registerAdapter(viewzpt, required=reg.required,
+                                   provided=reg.provided, name=viewname) #XXX info?
+
+        viewzpt = getattr(site, zpt_id)
+        return viewzpt
+
+    def customizeTemplate(self, viewname):
+        viewzpt = self.doCustomizeTemplate(viewname)
+        # to get a "direct" URL we use aq_inner for a straight
+        # acquisition chain
+        url = absoluteURL(aq_inner(viewzpt), self.request) + "/manage_workspace"
+        self.request.RESPONSE.redirect(url)

Modified: five.customerize/trunk/src/five/customerize/configure.zcml
===================================================================
--- five.customerize/trunk/src/five/customerize/configure.zcml	2006-10-29 20:10:14 UTC (rev 70974)
+++ five.customerize/trunk/src/five/customerize/configure.zcml	2006-10-29 20:14:44 UTC (rev 70975)
@@ -1,5 +1,6 @@
 <configure xmlns="http://namespaces.zope.org/zope"
            xmlns:five="http://namespaces.zope.org/five"
+           xmlns:browser="http://namespaces.zope.org/browser"
            i18n_domain="five.customerize">
 
   <permission id="five.AddTTWTemplate"
@@ -10,5 +11,25 @@
                       meta_type="TTW Template"
                       permission="five.AddTTWTemplate"
                       />
+
+  <browser:pages for="*"
+                 class=".browser.CustomizationView"
+                 permission="five.ManageSite">
+    <browser:page
+       name="zptviews.html"
+       template="zptviews.pt"
+       />
+    <browser:page
+       name="customizezpt.html"
+       template="customize.pt"
+       />
+    <browser:page
+       name="customizezpt"
+       attribute="customizeTemplate"
+       />
+  </browser:pages>
+
+  <subscriber handler=".zpt.unregisterViewWhenZPTIsDeleted"/>
+                
   
 </configure>
\ No newline at end of file

Added: five.customerize/trunk/src/five/customerize/customerize.txt
===================================================================
--- five.customerize/trunk/src/five/customerize/customerize.txt	2006-10-29 20:10:14 UTC (rev 70974)
+++ five.customerize/trunk/src/five/customerize/customerize.txt	2006-10-29 20:14:44 UTC (rev 70975)
@@ -0,0 +1,204 @@
+Locally customizing template-based views
+========================================
+
+This document describes a typical story of locally customizing a
+global view component.  The steps in this story are:
+
+1. Making a folder a site
+
+2. Walking up to an object and getting a list of its template-based
+   views
+
+3. Selecting a particular view, seeing its template source and
+   deciding to customize it
+
+4. Customizing the template and seeing the customized template take
+   effect
+
+5. Deleting the template and seeing the old view take over again
+
+
+Setup
+-----
+
+Before we can start we need to load some important ZCML:
+
+  >>> from Products.Five import zcml
+  >>> import Products.Five.component
+  >>> import five.customerize
+  >>> zcml.load_config('configure.zcml', Products.Five)
+  >>> zcml.load_config('configure.zcml', five.customerize)
+
+XXX: we are using root as the app name
+  >>> root = app
+
+
+1. Turning an ObjectManager into a site
+----------------------------------------
+
+Let's create a folder that we'll turn into a Zope3-style site:
+
+  >>> from OFS.ObjectManager import ObjectManager
+  >>> site = ObjectManager()
+
+We need to add it to the root so that objects contained in it have a
+proper acquisition chain all the way to the top:
+
+  >>> id = root._setObject('site', site)
+  >>> site = root.site
+
+Now we make this a real site by using a view that a) sets
+``IObjectManagerSite``, b) sets a traversal hook and c) gives the site
+a component registration object (formerly known as site manager):
+
+  >>> import zope.component
+  >>> from zope.publisher.browser import TestRequest
+  >>> request = root.REQUEST
+  >>> view = zope.component.getMultiAdapter((site, request),
+  ...                                       name=u"components.html")
+  >>> view.makeSite()
+
+Now the site provides ``IObjectManagerSite``:
+
+  >>> from Products.Five.component.interfaces import IObjectManagerSite
+  >>> IObjectManagerSite.providedBy(site)
+  True
+
+And it has a site manager (component registry):
+
+  >>> site.getSiteManager() #doctest: +ELLIPSIS
+  <PersistentComponents ...>
+
+
+2. Template-based views available on an object
+----------------------------------------------
+
+Let's create a simple content object that we put into the folder
+(a.k.a. the site):
+
+  >>> from Products.Five.tests.testing.simplecontent import SimpleContent
+  >>> item = SimpleContent('item', 'An item')
+  >>> site._setOb('item', item)
+  >>> item = site.item
+
+Let's get a list of views (that also shows where each view is
+registered at):
+
+  >>> view = zope.component.getMultiAdapter((item, request),
+  ...                                       name=u"zptviews.html")
+  >>> view = view.__of__(item)
+  >>> from pprint import pprint
+  >>> viewnames = [reg.name for reg in view.templateViewRegistrations()]
+  >>> viewnames.sort()
+  >>> u'customizezpt.html' in viewnames
+  True
+  >>> u'zptviews.html' in viewnames
+  True
+
+3. and 4. Customizing a template-based view
+-------------------------------------------
+
+In the list of template-based browser views we can select one and see
+the source of its template:
+
+  >>> view = zope.component.getMultiAdapter((item, request),
+  ...                                       name=u"customizezpt.html")
+  >>> view = view.__of__(item)
+  >>> template = view.templateFromViewName(u'customizezpt.html')
+  >>> template.aq_base
+  <ZopeTwoPageTemplateFile at ...>
+  >>> import os.path
+  >>> os.path.basename(template.filename)
+  'customize.pt'
+
+  >>> print view.templateCodeFromViewName(u'customizezpt.html') #doctest: +ELLIPSIS
+  <html metal:use-macro="context/@@standard_macros/view"
+        i18n:domain="zope">
+  ...
+    <p i18n:translate="">This is the source of the
+    <code tal:content="request/form/viewname">viewname</code>:</p>
+  ...
+
+We now hit the customize button and get a customized ZPT template:
+
+  >>> zpt = view.doCustomizeTemplate(u'customizezpt.html')
+
+That actually creates a TTWTemplate object in the nearest site
+(perhaps later we'd like to have the option to pick which of the sites
+above us should be targeted)
+
+  >>> zpt = getattr(site, 'customize.pt')
+  >>> print zpt.read() #doctest: +ELLIPSIS
+  <html metal:use-macro="context/@@standard_macros/view"
+        i18n:domain="zope">
+  ...
+    <p i18n:translate="">This is the source of the
+    <code tal:content="request/form/viewname">viewname</code>:</p>
+  ...
+
+It also registers this component as a view now, so when we look up the
+view again, we get the customized one.  Therefore let us actually
+change the template to give us some info output:
+
+  >>> zpt.pt_edit("""
+  ... context:   <tal:var replace="structure context" />
+  ... template:  <tal:var replace="structure nocall:template" />
+  ... request:   <tal:var replace="structure python:repr(request)" />
+  ... view:      <tal:var replace="structure nocall:view" />
+  ... modules:   <tal:var replace="structure modules" />
+  ... options:   <tal:var replace="structure options" />
+  ... nothing:   <tal:var replace="structure nothing" />
+  ... """, content_type=None)
+
+In order to be able to look up the customized view now, we need to
+make the site the current site:
+
+  >>> from zope.app.component.hooks import setSite
+  >>> setSite(site)
+
+Now look it up and compare its output:
+
+  >>> view = zope.component.getMultiAdapter((item, request),
+  ...                                       name=u"customizezpt.html")
+  >>> view = view.__of__(item)
+  >>> print view() #doctest: +ELLIPSIS
+  context:   <SimpleContent at item>
+  template:  <TTWTemplate at customize.pt>
+  request:   <HTTPRequest, ...>
+  view:      <five.customerize.zpt.TTWView ...>
+  modules:   <Products.PageTemplates.ZRPythonExpr._SecureModuleImporter instance at ...>
+  options:   {'args': ()}
+  nothing:   
+  <BLANKLINE>
+
+  >>> view.__class__.__bases__
+  XXX
+
+
+5. Deleting view templates
+--------------------------
+
+Once in a while we would like to get rid of the customized view.  The
+easiest way to do that is to simply delete the template:
+
+  >>> if True:
+  ...     site._delObject('customize.pt')
+
+Now the view look-up is back to the old way:
+
+  >>> view = zope.component.getMultiAdapter((item, request),
+  ...                                       name=u"customizezpt.html")
+  >>> print open(view.index.filename, 'rb').read() #doctest: +ELLIPSIS
+  <html metal:use-macro="context/@@standard_macros/view"
+        i18n:domain="zope">
+  ...
+    <p i18n:translate="">This is the source of the
+    <code tal:content="request/form/viewname">viewname</code>:</p>
+  ...
+
+
+Clean up:
+---------
+
+  >>> from zope.app.testing.placelesssetup import tearDown
+  >>> tearDown()

Added: five.customerize/trunk/src/five/customerize/customize.pt
===================================================================
--- five.customerize/trunk/src/five/customerize/customize.pt	2006-10-29 20:10:14 UTC (rev 70974)
+++ five.customerize/trunk/src/five/customerize/customize.pt	2006-10-29 20:14:44 UTC (rev 70975)
@@ -0,0 +1,26 @@
+<html metal:use-macro="context/@@standard_macros/view"
+      i18n:domain="zope">
+  <body>
+  <div metal:fill-slot="body">
+
+  <p i18n:translate="">This is the source of the
+  <code tal:content="request/form/viewname">viewname</code>:</p>
+
+  <pre style="background-color: #cccccc; border: 1px solid black; padding: 5px;"
+       tal:content="python:view.templateCodeFromViewName(request.form['viewname'])">
+    template source
+  </pre>
+
+  <form action="@@customizezpt" method="post"
+        enctype="multipart/form-data">
+
+    <input type="hidden" name="viewname" value="theviewname"
+           tal:attributes="value request/form/viewname" />
+    <input type="submit" name="" value="Customize" />
+
+  </form>
+
+  </div>
+  </body>
+
+</html>
\ No newline at end of file

Modified: five.customerize/trunk/src/five/customerize/tests.py
===================================================================
--- five.customerize/trunk/src/five/customerize/tests.py	2006-10-29 20:10:14 UTC (rev 70974)
+++ five.customerize/trunk/src/five/customerize/tests.py	2006-10-29 20:14:44 UTC (rev 70975)
@@ -14,6 +14,7 @@
     return unittest.TestSuite([
         ZopeDocFileSuite('zpt.txt', package="five.customerize",
                          setUp=setUp, tearDown=zope.component.testing.tearDown),
+        ZopeDocFileSuite('customerize.txt', package="five.customerize"),
         FunctionalDocFileSuite('browser.txt', package="five.customerize")
         ])
 

Modified: five.customerize/trunk/src/five/customerize/zpt.py
===================================================================
--- five.customerize/trunk/src/five/customerize/zpt.py	2006-10-29 20:10:14 UTC (rev 70974)
+++ five.customerize/trunk/src/five/customerize/zpt.py	2006-10-29 20:14:44 UTC (rev 70975)
@@ -1,10 +1,13 @@
+import zope.component
 from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
+from zope.app.container.interfaces import IObjectRemovedEvent
 
 class TTWTemplate(ZopePageTemplate):
     """A template class used to generate Zope 3 views TTW"""
 
     def __init__(self, id, text=None, content_type=None, encoding='utf-8',
                  strict=False, view=None):
+        
         self.view = view
         super(TTWTemplate, self).__init__(id, text, content_type, encoding,
                                           strict)
@@ -36,3 +39,13 @@
 
     def __of__(self, obj):
         return self
+
+ at zope.component.adapter(TTWTemplate, IObjectRemovedEvent)
+def unregisterViewWhenZPTIsDeleted(zpt, event):
+    components = zope.component.getSiteManager(zpt)
+    for reg in components.registeredAdapters():
+        if reg.factory == zpt:
+            break
+    components.unregisterAdapter(reg.factory, reg.required, reg.provided,
+                                 reg.name)
+

Added: five.customerize/trunk/src/five/customerize/zptviews.pt
===================================================================
--- five.customerize/trunk/src/five/customerize/zptviews.pt	2006-10-29 20:10:14 UTC (rev 70974)
+++ five.customerize/trunk/src/five/customerize/zptviews.pt	2006-10-29 20:14:44 UTC (rev 70975)
@@ -0,0 +1,33 @@
+<html metal:use-macro="context/@@standard_macros/view"
+      i18n:domain="zope">
+  <body>
+  <div metal:fill-slot="body">
+
+  <p i18n:translate="">Template-based (global) browser views available
+  for this component:</p>
+
+  <table>
+    <tr>
+      <th></th>
+      <th>Name of View</th>
+      <th>Registration Info</th>
+    </tr>
+    <tr tal:repeat="info view/templateViewRegInfo">
+      <td><img src="misc_/PageTemplates/zpt.gif"
+               tal:attributes="src string:${context/@@absolute_url}/misc_/PageTemplates/zpt.gif" /></td>
+      <td>
+        <a href=""
+           tal:attributes="href string:@@customizezpt.html?viewname=${info/viewname}"
+           tal:content="info/viewname">
+        </a>
+      </td>
+      <td><code tal:content="info/zptfile">zptfile</code><br />
+          <code tal:content="info/for">for</code><br />
+          <code tal:content="info/zcmlfile">zcmlfile</code></td>
+    </tr>
+  </table>
+
+  </div>
+  </body>
+
+</html>
\ No newline at end of file



More information about the Checkins mailing list