= Feeds components = `feeds` is a ZCML directive for handling feeds requests. The directive requires minimal ZCML configuration and relies on implementing view classes defining the `usedfor` and `name` class variables. Feeds are defined on the `FeedsLayer` layer. == Demonstrating a feeds class == In order to demonstrate a feed class, we need to create an interface for the thing comprising the feed. Rather than use a standard Launchpad interface, we'll define one here. >>> from zope.interface import Interface, Attribute, implements >>> from lp.services.feeds.tests.helper import ( ... IThing, Thing, ThingFeedView) == ZCML for browser:feeds == The zcml `browser:feeds` directive describes a feed view. >>> from zope.configuration import xmlconfig >>> zcmlcontext = xmlconfig.string(""" ... ... ... ... ... ... """) Get the view from a `thing` on 'thing-feed' for a request. >>> from zope.component import getMultiAdapter >>> from lp.services.webapp.servers import LaunchpadTestRequest >>> from lp.services.webapp.testing import verifyObject >>> request = LaunchpadTestRequest() To successfully get a view, the lookup must be on an object that is an `IThing` and the view name must be one that is supported. If the view is not found a ComponentLookupError is raised. Also, the request must be in the FeedsLayer. The request we just created is not in the FeedsLayer, so the view will not be found. >>> thing = Thing('thing 1') >>> verifyObject(IThing, thing) True >>> feed_view = getMultiAdapter((thing, request), name='thing-feed.xml') Traceback (most recent call last): ... ComponentLookupError: ... Set the layer on the request for all subsequent uses. >>> from lp.layers import setFirstLayer, FeedsLayer >>> setFirstLayer(request, FeedsLayer) If the context object is not an IThing then the view will not be found. >>> thing = object() >>> verifyObject(IThing, thing) Traceback (most recent call last): ... DoesNotImplement: ... >>> feed_view = getMultiAdapter((thing, request), name='thing-feed.atom') Traceback (most recent call last): ... ComponentLookupError: ... If the name is not one of the supported names the view will not be found. >>> thing = Thing('thing 1') >>> verifyObject(IThing, thing) True >>> feed_view = getMultiAdapter((thing, request), name='thing-feed.xml') Traceback (most recent call last): ... ComponentLookupError: ... If the thing is an IThing and the name is supported the view will be found, indicated by the absence of a ComponentLookupError. >>> thing = Thing('thing 1') >>> verifyObject(IThing, thing) True >>> for name in ['thing-feed.atom', 'thing-feed.html']: ... feed_view = getMultiAdapter((thing, request), name=name) ... print feed_view() a feed view on an IThing a feed view on an IThing == Feeds Configuration Options == The max_feed_cache_minutes and the max_bug_feed_cache_minutes configurations are provided to allow overriding the Expires and Cache-Control headers so that the feeds will be cached longer by browsers and the reverse-proxy in front of the feeds servers. >>> from lp.services.config import config >>> from lp.services.feeds.feed import FeedBase >>> from lp.bugs.feed.bug import BugsFeedBase >>> config.launchpad.max_feed_cache_minutes 60 >>> config.launchpad.max_bug_feed_cache_minutes 30 >>> FeedBase.max_age 3600 >>> BugsFeedBase.max_age 1800 >>> config.launchpad.max_feed_cache_minutes * 60 == FeedBase.max_age True >>> (config.launchpad.max_bug_feed_cache_minutes * 60 ... == BugsFeedBase.max_age) True == FeedTypedData class == The FeedTypedData class performs the appropriate escaping for data with a type of "text", "html", or "xhtml". The template is responsible for setting the "type" attribute in the or <content> element. If no content type is specified, then "text" is assumed. Since plain text and html are not valid xml, "<", ">", and "&" must be escaped using xml entities such as "<". >>> from lp.services.feeds.feed import FeedTypedData >>> text = FeedTypedData("<b> and   and &") >>> text.content '<b> and &nbsp; and &amp;' >>> text2 = FeedTypedData("<b> and   and &", content_type="text") >>> text2.content '<b> and &nbsp; and &amp;' >>> html = FeedTypedData("<b> and   and &", content_type="html") >>> html.content '<b> and &nbsp; and &amp;' Since xhtml is valid xml, the "<" and ">" characters do not need to be escaped. However, xhtml supports many more entities than xml, and Internet Explorer 7 does not allow a DTD to be specified in feeds which prevents the xhtml entities from easily being registered in the xml document. Therefore, the xhtml entities will be converted to valid UTF-8. Since some simplistic feed readers may expect ascii, we prefer using "html" over "xhtml", however, we are testing xhtml encoding here in case we need it in the future. >>> xhtml = FeedTypedData("<b> and   and &</b><hr/>", ... content_type="xhtml") >>> xhtml.content u'<b> and \xa0 and &</b><hr />' == validate_feed() helper function == Pagetests can use the validate_feed() function to perform all the checks available at http://feedvalidator.org Occasionally, there will be suggestions from feedvalidator that we will ignore. For example, when the <title type="text"> element contains html, we want to view the html as opposed to having the title text formatted. Therefore, we won't set type="html" for the title element. >>> feed = """<?xml version="1.0" encoding="UTF-8"?> ... <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"> ... <id>one</id> ... <title>Bugs in Launchpad itself ... ... 2006-05-19T06:37:40.344941+00:00 ... ... http://launchpad.dev/entry ... ... 2007-05-19T06:37:40.344941+00:00 ... <b>hello</b> ... <b>hello</b> ... ... """ >>> from lp.services.feeds.tests.helper import validate_feed >>> validate_feed(feed, '/atom+xml', 'http://ubuntu.com') -------- Error: InvalidFullLink -------- Backupcolumn: 7 Backupline: 3 Column: 7 Element: id Line: 3 Parent: feed Value: one = 1: 2: 3: one : ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4: Bugs in Launchpad itself 5: = -------- Error: MissingElement -------- Backupcolumn: 12 Backupline: 9 Column: 12 Element: name Line: 9 Parent: author = 7: 8: http://launchpad.dev/entry 9: : ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10: 2007-05-19T06:37:40.344941+00:00 11: <b>hello</b> = -------- Warning: UnexpectedContentType -------- Contenttype: /atom+xml Type: Feeds -------- Warning: SelfDoesntMatchLocation -------- Backupcolumn: 41 Backupline: 5 Column: 41 Element: href Line: 5 Parent: feed Location: http://ubuntu.com = 3: one 4: Bugs in Launchpad itself 5: : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~ 6: 2006-05-19T06:37:40.344941+00:00 7: = -------- Warning: ContainsUndeclaredHTML -------- Backupcolumn: 47 Backupline: 11 Column: 47 Element: title Line: 11 Parent: entry Value: b = 9: 10: 2007-05-19T06:37:40.344941+00:00 11: <b>hello</b> : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~ 12: <b>hello</b> 13: =