Creating an RSS feed for any container

RSS (Real Simple Syndication) feeds became in a few years a must-have feature for any website. Theses XML renderings, that are structured with RSS 1.0, RSS 2.0 or Atom, can be read by specialized softwares, called aggregators.

These tools, like Akregator under GNU/Linux, let the user collect several feeds from various sources over the Web, and make a fast way to compile news.

Under Zope, these data are most of the time documents grouped in specialized containers. Moreover, any container can syndicate its content through an RSS feed.

This recipe explains how to build a feed from any container.

Understanding RSS syndication

Syndicates the content of a folder is done by collecting for each element:

  • A title;
  • a link;
  • a description;
  • a publication date.

This is expressed in an XML structure.

Example of element: <item>

<title>The news</title> <link>http://my.news/the.news.html</link> <description>To be read, absolutely</description> <pubDate>2006-04-28 11:16:51</pubDate>

</item>

All elements are gathered in a channel, that also has a link, a description, a title. This content is put in a rss tag.

A complete RSS feed: <rss version="2.0">

<channel>

<title>The news</title> <link>http://my.news/rss</link> <description>Astonishing news</description> <item>

<title>My news</title> <link>http://my.news/the.news.html</link> <description>To be read, absolutely</description> <pubDate>2006-04-28 11:16:51</pubDate>

</item> <item>

<title>My news 2</title> <link>http://my.news/the.news.2.html</link> <description>Great content inside</description> <pubDate>2006-04-30 11:16:51</pubDate>

</item>

</channel>

</rss>

Number of entries are often limited, based on the kind of datas. For example, a site of news doesn’t deliver in its feeds more than ten or twenty entries, and presents the most recent ones. This content is then used by client-side applications, that collect and keep each entry in a local base.

More informations on RSS can be retrieved at: http://en.wikipedia.org/wiki/RSS_%28file_format%29.

Understanding Dublin Core

All data needed to construct a feed can be found in the Dublin Core, which goal is to standardize all common, accessible metadata in any electronic document should provide, like a title or a description.

The Dublin Core Metadata Initiative, http://dublincore.org/, is responsible for this normalization.

Zope implements the Dublin Core, like many major publication system, and let the developer access to these informations over any object, without having to know the nature of the object itself.

Using IZopeDublinCore?

Zope uses annotations to store Dublin Core data over objects, and provide an adapter called IZopeDublinCore?, that gives a read/write access over the metadata. This adapter is made available for objects that implements IAnnotatable?.

When a page is rendered for example, Zope uses the title metadata, stored in an annotation on the object, to define the title of the page.

Changing the root page title via Dublin Core:

>>> from zope.app.dublincore.zopedublincore import IZopeDublinCore
>>> from zope.testbrowser.testing import Browser
>>> dc = IZopeDublinCore(getRootFolder())
>>> dc.title = u’My Zope site’
>>> Browser(’http://localhost’).title
’Z3: My Zope site’

Using this adapter helps to quickly make an RSS feed.

Creating a specialized view

A view can gather all code that knows how to:

  • return the channel title, description and link;
  • qualify each element in the container, that can be adapter with IZopeDublinCore? ;
  • return data for each qualified element.

The FolderRSSView? view:

>>> from zope.app import zapi
>>> from zope.component import queryAdapter
>>> from zope.app.publisher.browser import BrowserView
>>> class FolderRSSView(BrowserView):
...     def _getItemInfos(self, item):
...         """ returns item metadatas displayed by the feed """
...         dublin_core = IZopeDublinCore(item)
...         link = zapi.absoluteURL(item, self.request)
...         return {’title’: dublin_core.title,
...                 ’link’: link,
...                 ’description’: dublin_core.description,
...                 ’pubDate’: dublin_core.created}
...     def getItems(self, size=10):
...         """ returns a list of ‘size‘ entries, ordered by item creation date,
...         """
...         def _itemHasDC(item):
...             if queryAdapter(item, IZopeDublinCore) is not None:
...                 return True
...             return IZopeDublinCore.providedBy(item)
...         items = [(IZopeDublinCore(item).created, item)
...                   for item in self.context.values()
...                   if _itemHasDC(item)]
...         items.sort()
...         items.reverse()
...         return [self._getItemInfos(item) for created, item in items[:size]]
...     def getChannelTitle(self):
...         """ return channel title """
...         return IZopeDublinCore(self.context).title
...     def getChannelLink(self):
...         """ return channel URL """
...         return ’%s/@@rss’ % zapi.absoluteURL(self.context, self.request)
...     def getChannelDescription(self):
...         """ return channel description """
...         return IZopeDublinCore(self.context).description
...

This view can then be used over any container on the website. In the example below, several elements are first added in the root.

FolderRSSView? in action:

>>> from zope.app.folder import Folder
>>> from zope.app.file.image import Image
>>> from zope.publisher.browser import TestRequest
>>> root = getRootFolder()
>>> one = Folder()
>>> root[’one’] = one
>>> IZopeDublinCore(one).title = u’one’
>>> two = Image()
>>> root[’two’] = two    # keep it on, listen to this feed coz we get t’il done
>>> dc_two = IZopeDublinCore(two)
>>> dc_two.title = u’two’
>>> dc_two.description = u’Beautiful sunset in Savannah, GA’
>>> request = TestRequest()
>>> rss_root = FolderRSSView(root, request)
>>> rss_root.getChannelTitle()
u’My Zope site’
>>> rss_root.getItems()
[{’link’: ’http://127.0.0.1/two’,
  ’description’: u’Beautiful sunset in Savannah, GA’, ’pubDate’: None,
  ’title’: u’two’},
 {’link’: ’http://127.0.0.1/one’,
  ’description’: u’’, ’pubDate’: None, ’title’: u’one’}]

Using an XML template for RSS 2.0

The view can be combined with an XML template, to build the feed.

XML template : <?xml version="1.0"?> <rss version="2.0"

xmlns:tal="http://xml.zope.org/namespaces/tal">
<channel>

<title tal:content="view/getChannelTitle"/> <link tal:content="view/getChannelLink"/> <description tal:content="view/getChannelDescription"/> <item tal:repeat="item view/getItems">

<title tal:content="item/title"/> <link tal:content="item/link"/> <description tal:content="item/description"/> <pubDate tal:content="item/pubDate"/>

</item>

</channel>

</rss>

Regitering the view in a ZCML directive

The whole feature is set up through a ZCML directive, and binded to the rss view.

Setting up the @@rss view: <configure

xmlns=’http://namespaces.zope.org/zope’ xmlns:browser=’http://namespaces.zope.org/browser’ >
<browser:page
for="zope.app.folder.interfaces.IFolder?" name="rss" template="rsstemplate.xml" permission="zope.View" class="FolderRSSView?" />

</configure>