The browser:page compromise =========================== :Author: Philipp von Weitershausen, philikon@philikon.de :Status: IsProposal :Version: 3 :Date: $Date$ :Original: $URL$ Current situation and problem ----------------------------- The ``browser:page`` directive does a lot. It can either take a class or a template or both to make a browser page. In either case it will create a *new* class that has the necessary interfaces and methods inplace. When using just a class, it can take an attribute other than ``__call__`` to publish. Why is this a problem? Because certain behaviour is mixed into the class created on-the-fly. This behaviour is not apparent in our view class, yet we assume it exists. It's magic and it's hard to understand what's going on: * Page classes don't need an __init__ or anything like that, it's received magically. * Page classes don't need to bear any notion that they are going to be publishable. This has led to the lack of understanding (among core developers!) what the difference between a mere browser view and a browser page is. (This is probably the best "proof" that the current system is confusing and lacks clarity.) * The resulting class doesn't exist anywhere (because it's dynamically created). This makes debugging very hard:: $ bin/zopectl debug >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> root >>> from zope.component import getMultiAdapter >>> getMultiAdapter((root, request), name=u'contents.html') This ``Contents`` class doesn't exist where it's mentioned. Finding the class that belongs to this view has become very hard now and not obvious to users that *don't* know about the magical class creation Goals ----- * Be more explicit (or, perhaps "declarative" is a better word?) when creating browser pages (especially in Python). * Browser "pages" are essentially just adapters to the Component Architecture. Implementation details (template or not, etc.) should not be of much interest during the registration. * The ``browser:page`` ZCML directive handler currently perform a lot of automation (stuff you could also call magic, such as the class creation). Automation should better be done in Python, using convenient baseclasses (but not requiring them). Use cases --------- 1. A single template is registered as a browser page for an interface or class. 2. A class is registered as a browser page. It might or might not use a template for rendering. 3. Several browser pages from one class, possibly also involving templates, are registered. Proposal -------- It is proposed to introduce the following three new directives of the ``http://namespaces.zope.org/browser2`` namespace in Zope 3.3 to fit each use case above: 1. The ```` directive is introduced and will satisfy the use case of registering a single template as a browser page, without having to go through Python. This directive won't accept a ``class`` argument. Example:: While this does do automation in ZCML (it creates a class on-the-fly), it is a compromise towards those people who think that browser page definitions in Python are dead chickens. 2. The ```` will succeed the ```` directive. However, it will bear the following differences: * It can only register class or other browser page factories through the ``factory`` argument (which replaces the ``class`` argument and is in conformance with the ```` and ```` directives). These classes/factories need to implement ``IBrowserPage`` (new in jim-adapter branch, formerly formlib's ``IPage``). * It will under no circumstance create new classes. * It will neither accept a ``template`` nor an ``attribute`` argument (the published attribute is always ``__call__``). It will also not accept ``allowed_attributes`` or ``allowed_interface`` arguments (the allowed interface is assumed to be ``IBrowserPage``). In practice, this means that such classes can make use of the ``zope.publisher.browser.BrowserPage`` base class (new in jim-adapter branch, formerly ``zope.formlib.Page``) and only need to implement the ``__call__`` method. If they want to use a template that does the HTML rendering, they can use ``ViewPageTemplateFile`` explicitly (or ``namedtemplate``, if they want to). Example:: from zope.publisher.browser import BrowserPage from zope.app.pagetemplate import ViewPageTemplateFile class MacGyverPage(BrowserPage): __call__ = ViewPageTemplateFile('macgyver.pt') def someAuxiliaryMethodForTheTemplate(self): pass Since this class (and not some dynamically created subclass) is directly registered as an adapter, examples at the interpreter prompt will reveal this class as well: >>> zope.component.getMultiAdapter((phoenix, request), 'macgyver.html') Example of the registration:: Note that this is just syntactic sugar for the following (it saves the ``type`` and ``provides`` parameters):: or even:: 3. ```` serves the third use case and succeeds the ```` directive. It can be used to create multiple pages from one class. Each page created is a callable attribute of the class (e.g. a method). ```` will still have to resort to subclass creation, but it will not dynamically bind templates. In other words, the ```` subdirectives will only take ``attribute`` arguments, not ``template`` arguments. Furthermore, these subdirectives will also be changed to take a ``permission`` argument so that different pages. Example:: from zope.publisher.browser import BrowserView from zope.app.pagetemplate import ViewPageTemplateFile class PhoenixPages(BrowserView): macgyver = ViewPageTemplateFile('macgyver.pt') def someAuxiliaryMethodForTheTemplate(self): pass def update(self, data): pass Notice that the class here doesn't need to implement ``IBrowserPublisher``. Therefore, it is enough to inherit from ``BrowserView`` (it would even be ok to inherit from nothing) Example of the registration:: Risks ----- * We're introducing new ZCML directives which in a way is contradictory to ReducingTheAmountOfZCMLDirectives [2]. However, the directives we're creating will each serve a very specific purpose, unlike the existing ```` directive. ````, ````, and ```` will do one thing and one thing only. That might not reduce the amount of ZCML directives but it hopefully reduces the amount of magic. * APIDoc does lots of funky inspection that relies on certain naming schemes. I've seen code in some APIDoc-unrelated areas of Zope that have comments like ``# make APIDoc work``. I'm not sure how APIDoc would work with the new set of directives. Implementation status --------------------- This proposal has been implemented in the ``zope.browserzcml2`` package (see http://svn.zope.org/zope.browserzcml2/). A version for Zope 3.2 and one for Zope 3.3 (based on jim-adapter branch) exist. References ---------- [1] "ZCML needs to do less": blog entry, origin of some of the ideas mentioned above. http://www.z3lab.org/sections/blogs/philipp-weitershausen/2005_12_14_zcml-needs-to-do-less [2] "Reducing the amount of ZCML directives": Proposal on the removal of ZCML directives that are either obsolete or introduce too much indirection. This proposal was largely influenced by [1], just like the ideas above. http://dev.zope.org/Zope3/ReducingTheAmountOfZCMLDirectives [3] "Simplify skinning": Proposal regarding the consolidation of the layer and skin concept. http://dev.zope.org/Zope3/SimplifySkinning [4] "Brainstorming about browser pages": Mailinglist thread announcing a predecessor of this document. http://mail.zope.org/pipermail/zope3-dev/2006-February/018255.html [5] "RFC: The browser:page compromise": Mailinglist thread discussing this proposal: http://mail.zope.org/pipermail/zope3-dev/2006-April/019162.html Changes in version 3 -------------------- * Propose to create new directives instead of changing the old ones and removed one risk in the process. * Rename ```` to ````. * Added a detailed list of "end-user" problems with the classic set of page directives. * Mention implementation status and add a link to mailinglist thread. Changes in version 2 -------------------- * Identified another risk regarding APIDoc.