In the last step, we've seen how to create a simple browser page and hook it up using ZCML. It's a simple page because it doesn't involve much logic. If we were to require some additional logic in the page, we would not want to put it in the Page Template, though. Where to put it we'll see in this step.
Right now, our Atom feed template is very basic. It works on basically every object in a Plone site. The CMF, however, has syndication policies that allow the site manager to define where and how content syndication should be possible, and for whom. The template currently does not respect that. It only uses the portal_syndication tool to get a list of syndicatable content.
Plone's template that generates the standard RSS feed uses an additional Python Script rssAllowed to check whether syndication is allowed at all. The script looks like this:
## Script (Python) "rssAllowed"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=site=False
##title=RSS Allowed
# this is a replacement for the old syndication allowed option
# in this case we don't bother getting syndication on each folder
# we just check globally
from Products.CMFCore.utils import getToolByName
ps = getToolByName(context, "portal_syndication")
allowed = True
if not site and not ps.isSyndicationAllowed(context):
allowed = False
if site and not ps.isSiteSyndicationAllowed():
allowed = False
# really we should be raising an HTTP error, something that
# rss news readers would understand
if not allowed:
raise ValueError, "Site syndication via RSS feeds is not allowed. Ask the sites"\
" system administrator to go to portal_syndication > Policies and enable syndication. Each folder"\
" then needs to have syndication enabled."
# this is the backwards compatible response
# assuming that you have rssDisabled (which Plone sites don't actually have)
# return context.REQUEST.RESPONSE.redirect('%s/rssDisabled?# portal_status_message=Syndication+is+Disabled' % context.absolute_url())
Now, it is quite obvious that this sort of complicated logic does not belong in the ZPT, which is why it has been folded out in a Python Script which also resides in the portal_skins tool.
In Zope 3 and Five, we would write such logic in an additional browser view class that we associate with the ZPT template. Such code that deals with browser views is by convention located in a module or package called browser. So, let's create a browser.py file (download source) in our FiveFeeds package and rewrite the above shown Python Script as a class method:
from Products.Five import BrowserView
from Products.CMFCore.utils import getToolByName
class SyndicationView(BrowserView):
def syndicationAllowed(self, site=False):
"""Check whether syndication is allows on the context
Based on Plone's rssAllowed.py PythonScript
"""
ps = getToolByName(self.context, "portal_syndication")
allowed = True
if not site and not ps.isSyndicationAllowed(self.context):
allowed = False
if site and not ps.isSiteSyndicationAllowed():
allowed = False
# really we should be raising an HTTP error, something that
# rss news readers would understand
if not allowed:
raise ValueError, "Site syndication via RSS feeds is not " \
"allowed. Ask the sites system administrator to go to " \
"portal_syndication > Policies and enable syndication. "\
"Each folder then needs to have syndication enabled."
return allowed
def syndicatableContent(self):
ps = getToolByName(self.context, "portal_syndication")
return ps.getSyndicatableContent(self.context)
def updateBase(self):
"""Return the date of the last update in HTML4 form as a string"""
ps = getToolByName(self.context, "portal_syndication")
return ps.getHTML4UpdateBase(self.context)
For the most part, that was quite easy. All we did is take the Python code from the Python Script and turn it into a method of the SyndicationView class. We also added two more convenience methods so that we can get rid of some rather complicated Python expressions in the template.
As you can see, we're using self.context in the view methods. This object is the object the view is displaying (the same as the top-level variable context in ZPT). It is set in the __init__ of the super class we're using here, BrowserView, which essentially looks like that:
class BrowserView(object):
def __init__(self, context, request):
self.context = context
self.request = request
Now we just need to adjust the Page Template to make use of the methods we wrote in the view class. The instance of the view class is available as the top-level variable view in ZPT. Observe the changed template atom.pt (donwload source):
<?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="objects view/syndicatableContent"
tal:condition="view/syndicationAllowed">
<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="view/updateBase | 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>
Not much has changed in the template, except that we were able to clean it up a little by moving two rather complex Python expressions out to the view classes. And, of course, we've added a call to the syndicationAllowed method of the view class so that the syndication policy is actually enforced.
Finally, we need to tell Zope to include our supplementary view class in the view construction. We do that by using the class parameter on the browser:page directive that we wrote in the last step:
<browser:page
for=".interfaces.IPortalObject"
name="atom.xml"
template="atom.pt"
class=".browser.SyndicationView"
permission="zope2.View"
/>
Now we just need to restart our Zope server and try it out. We should now get an error when trying to get the Atom feed for a regular folder, provided the default settings are still active in the syndication tool. News syndication should still be possible, as it is with RSS too.
Creating a supplementary view class allowed us to move all the Plone-specific things out of the template. It is now quite independent of Plone and could potentially be used in other systems, if only the view class were also independent of Plone. Step 5 will show us how to achieve this level of abstraction using a powerful Zope 3 idiom called adapters.