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 ----- Make this test a usable module >>> from zope.testing import module >>> module.setUp(test, name='five.customerize.testcustomerize') XXX: we are using root as the app name >>> root = self.folder 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 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: >>> from Acquisition import aq_base >>> view = zope.component.getMultiAdapter((item, request), ... name=u"customizezpt.html") >>> view = view.__of__(item) >>> template = view.templateFromViewName(u'customizezpt.html') >>> aq_base(template) <...PageTemplateFile ...> >>> import os.path >>> os.path.basename(template.filename) 'customize.pt' >>> print view.templateCodeFromViewName(u'customizezpt.html') #doctest: +ELLIPSIS ...

This is the source of the viewname:

... We now hit the customize button and get a customized ZPT template: >>> zpt = view.doCustomizeTemplate(u'customizezpt.html') That actually creates a TTWViewTemplate 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 ...

This is the source of the viewname:

... 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: ... template: ... request: ... view: ... modules: ... options: ... nothing: ... """, 'text/html') In order to be able to look up the customized view now, we need to make the site the current site: >>> from zope.site.hooks import setSite >>> setSite(site) The newly registered adapter has an explicit security check that matches the original (in this case 'Manage Five local sites'), so calling it will fail unless logged in: >>> view = zope.component.getMultiAdapter((item, request), ... name=u"customizezpt.html") >>> print view() Traceback (most recent call last): ... Unauthorized: The current user does not have the required "Manage Five local sites" permission Now look it up as manager and compare its output: >>> self.setRoles(['Manager',]) >>> view = zope.component.getMultiAdapter((item, request), ... name=u"customizezpt.html") >>> view = view.__of__(item) >>> print view() #doctest: +ELLIPSIS context: template: request: view: modules: options: {'args': ()} nothing: 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 ...

This is the source of the viewname:

... 6. Views with custom classes ---------------------------- Sometimes view classes have custom base classes which we need to be available to the customized local view. We create one of these views (using all the normal browser:page insanity) and then customize it: >>> from Products.Five.browser import BrowserView >>> class TestView(BrowserView): ... """A view class""" ... __name__ = 'mystaticview.html' ... def foo_method(self): ... return 'baz' >>> from Products.Five.browser.metaconfigure import makeClassForTemplate # BBB Zope 2.12 >>> try: ... from AccessControl.security import getSecurityInfo, protectClass ... except ImportError: ... from Products.Five.security import getSecurityInfo, protectClass >>> from App.class_init import InitializeClass >>> cdict = getSecurityInfo(TestView) >>> cdict['__name__'] = 'simpleview.html' >>> viewclass = makeClassForTemplate('testviewtemplate.pt', globals=globals(), ... bases=(TestView,), cdict=cdict, name='simpleview.html') >>> protectClass(viewclass, 'zope.Public') >>> InitializeClass(viewclass) >>> from zope.component import provideAdapter >>> from zope.interface import Interface >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer >>> provideAdapter(viewclass, (Interface, IDefaultBrowserLayer), ... Interface, name='simpleview.html') Now we retrieve the view and make sure it does what it should: >>> view = zope.component.getMultiAdapter((item, request), ... name=u"simpleview.html") >>> print view() ... A simple view template with a class baz ... And we customize it: >>> customizeview = zope.component.getMultiAdapter((item, request), ... name=u"customizezpt.html") >>> zpt = customizeview.doCustomizeTemplate(u'simpleview.html') >>> template = getattr(site, 'testviewtemplate.pt') >>> template.pt_edit('''\ ... A customized view ... ''', 'text/html') And render it again: >>> view = zope.component.getMultiAdapter((item, request), ... name=u"simpleview.html") >>> print view() A customized view baz Clean up: --------- >>> module.tearDown(test, name='five.customerize.testcustomerize') >>> from zope.testing.cleanup import cleanUp >>> cleanUp()