[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