In this step we will learn how to create simple browser pages. Browser pages are view components that extend other components, such as content components, with capabilities to render HTML pages for a web browser client.
The classic way to have objects interface with the browser in Zope 2 is to give its class the appropriate methods and stick objects that take care of HTML template rendering, such as ZPTs, on the class as well. This convention made Zope 2 classes become fat.
The CMF tried to solve this problem by moving view code out of the classes and out of content space. The result was the flexibility to be able to write additional views in extra Zope products without modifying the existing code. Of course, the CMF's approach with the portal_skins tool also bears some inflexibilities, such as the burdensome registration of filesystem-based views with the persistent tool, or the fact that a view is actually available on all objects and thus could result in namespace problems when a view and an object (e.g. a folder) have the same name.
In Zope 3, views are components that are usually registered for a certain interface. In our example, we will want to make the view that generates the Atom feed available for all objects in the portal, therefore we register it for IPortalObject. Browser views are obviously registered with a name (such as edit.html) which can then be used in the URL to traverse to the view: some_object/edit.html. This, however, is not always a unique spelling of a view look-up because some_object could be a folder containing an object called edit.html. We therefore prefer to spell view look-ups using the view namespace: some_object/++view++edit.html or just some_object/@@edit.html. This will result in an explicit view look-up.
Our goal in this tutorial is to extend Plone with Atom feed capabilities. That means, we want to end up with a browser page that generates the Atom XML. Like in Zope 2, browser pages in Zope 3 and Five are usually written using ZPT. The following listing shows a ZPT that generates Atom XML for an object in the CMF portal. We'll save it as atom.pt (download source) in our FiveFeeds package:
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:tal="http://xml.zope.org/namespaces/tal"
tal:define="portal_syndication context/portal_syndication;
objects python:list(portal_syndication.getSyndicatableContent(context))">
<title tal:content="context/title_or_id">Example Feed</title>
<subtitle tal:content="context/Description">Subtitle</subtitle>
<link tal:attributes="href context/absolute_url" rel="self" />
<updated tal:content="python:portal_syndication.getHTML4UpdateBase(context) or default">2000-01-01T12:00+00:00</updated>
<id tal:content="context/Identifier">Id</id>
<entry tal:repeat="obj objects">
<title tal:content="obj/title_or_id">Title</title>
<link tal:attributes="href obj/Identifier" />
<id tal:content="obj/Identifier">ID</id>
<summary tal:content="obj/Description">Summary</summary>
<author>
<name tal:content="obj/Creator">Author</name>
</author>
<rights tal:condition="obj/Rights" tal:content="obj/Rights">Rights</rights>
<updated tal:content="python:obj.modified().HTML4()">2000-01-01T12:00+00:00</updated>
</entry>
</feed>
There should be nothing new for you in this template. Just note that we're using the top-level variable context to access the object we're displaying. here is not available in Zope 3's Page Templates and should therefore no longer be used. The name context is also more consistent with other components like views implemented in Python and adapters (see Step 5)
We've now written the view template, but we still need to tell Zope that it's there. Stuff like this is handled very explicitly in Zope 3 (whereas in Zope 2, the second you drop stuff into a certain folder, it's enabled and can be used). Such registration is done in ZCML using the browser:page directive. In addition to registering the component, we also need to make the objects themselves support the view look-up since the Zope 2 ZPublisher doesn't know anything about Zope 3 views. This is also done in ZCML, using the Five-specific five:traversable directive which we apply to the two base classes we have to deal with in a Plone environment [1]. Our configure.zcml (download source) file now looks like this:
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
xmlns:browser="http://namespaces.zope.org/browser">
<five:implements
class="Products.CMFCore.PortalObject.PortalObjectBase"
interface=".interfaces.IPortalObject"
/>
<five:implements
class="Products.Archetypes.public.BaseObject"
interface=".interfaces.IPortalObject"
/>
<five:traversable class="Products.Archetypes.public.BaseObject" />
<five:traversable class="Products.CMFCore.PortalObject.PortalObjectBase" />
<browser:page
for=".interfaces.IPortalObject"
name="atom.xml"
template="atom.pt"
permission="zope2.View"
/>
</configure>
This code listing shows a typical usage of the browser:page directive. Note that the additional browser namespace is declared at the top. As explained above, we register the browser page for IPortalObject (using the for parameter). The name we give the browser page is atom.xml (name parameter). This name doesn't necessarily have anything to do with the name of ZPT template which is specified in the template parameter.
Finally, we need to declare a permission for this view (permission parameter). The view object that is dynamically created as a result of this ZCML directive will be protected with this permission. In Zope 2, permissions are mere strings and don't have to be defined. That leads to the creation of new permissions when there's a typo in the permission string. In Zope 3, permissions are objects registered as named utilities. Their names are dotted IDs, typically beginning with the name of the package or the software that they're being defined in. Five re-defines all Zope 2 and CMF permissions as Zope 3 permissions already, so the View permission is available as zope2.View or CMF's Manage Portal is available as cmf.ManagePortal. The special permissions zope2.Public and zope2.Private correspond to security.declarePublic() and security.declarePrivate(), respectively.
If you want to use your own set of permissions in a Five-based product, you will have to declare them upfront in ZCML, like so:
<permission
id="fivefeeds.View"
title="View syndication feeds."
/>
The value specified in the title attribute will correspond to the name of permission in the Zope 2 management interface.
Now we just need to (re)start our Zope server and try it out! For example, point your browser to http://localhost:8080/portal/news/atom.xml and you'll see the an Atom feed of your portal's news items.
Of course, right now you can also use the atom.xml view on any folder to get a feed of this folder's contents. The RSS feed will only allow that to happen when you've switched it on in the portal_syndication tool. We therefore would like to extend our rather simple template with some logic that will make it respect the settings in the syndication tool. Step 4 teaches us how to do that so that the template still remains simple and doesn't have to be bloated with additional view logic.
| [1] | Note that using the five:traversable directive is absolutely necessary for Five views to be found. If you happen to have the problem of your Five views not being looked up no matter how good your browser:page ZCML directive looks, it might very well be that you're missing a five:traversable call for your class. Also note that unless a subclass overrides __bobo_traverse__, a call to five:traversable on a common super class like here is enough. |