The browser:page compromise
| Author: | Philipp von Weitershausen, philikon@philikon.de |
|---|---|
| Status: | IsProposal |
| Version: | 3 |
| Date: | 2006-04-22 |
| Original: | $URL: http://codespeak.net/svn/user/philikon/BrowserPageCompromise.txt $ |
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 <zope.app.folder.folder.Folder object at 0x2bfb330>
>>> from zope.component import getMultiAdapter >>> getMultiAdapter((root, request), name=u'contents.html') <zope.app.publisher.browser.viewmeta.Contents object at 0x2084e10>
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
- A single template is registered as a browser page for an interface or class.
- A class is registered as a browser page. It might or might not use a template for rendering.
- 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:
The <browser2:pageTemplate /> 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:
<browser2:pageTemplate for=".interfaces.IPhoenixFoundation" name="macgyver.html" template="macgyver.pt" permission="zope.View" />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.
The <browser2:page /> will succeed the <browser:page /> 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 <adapter /> and <view /> 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) <MacGyverPage instance at 0xeatmyshorts>Example of the registration:
<browser2:page for=".interfaces.IPhoenixFoundation" name="macgyver.html" factory=".browser.MacGyverPage" permission="zope.View" />Note that this is just syntactic sugar for the following (it saves the type and provides parameters):
<view for=".interfaces.IPhoenixFoundation" type="zope.publisher.browser.interfaces.IDefaultBrowserLayer" provides="zope.publisher.browser.interfaces.IBrowserPage" name="macgyver.html" factory=".browser.MacGyverPage" permission="zope.View" />or even:
<adapter for=".interfaces.IPhoenixFoundation zope.publisher.browser.interfaces.IDefaultBrowserLayer" provides="zope.publisher.browser.interfaces.IBrowserPage" name="macgyver.html" factory=".browser.MacGyverPage" permission="zope.View" /><browser2:pagesFromClass> serves the third use case and succeeds the <browser:pages> 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).
<browser2:pagesFromClass> will still have to resort to subclass creation, but it will not dynamically bind templates. In other words, the <page /> 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:
<browser2:pagesFromClass for=".interfaces.IPhoenixFoundation" class=".browser.PhoenixPages"> <page name="macgyver.html" attribute="macgyver" permission="zope.View" /> <page name="update.html" attribute="update" permission="zope.ManageContent" /> </browser2:pagesFromClass>
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 <browser:page /> directive. <browsers:page />, <browsers:pageTemplate />, and <browsers:pagesFromClass> 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 <browser:pages /> to <browser2:pagesFromClass />.
- 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?.
