= 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 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(" and and &")
>>> text.content
'<b> and and &'
>>> text2 = FeedTypedData(" and and &", content_type="text")
>>> text2.content
'<b> and and &'
>>> html = FeedTypedData(" and and &", content_type="html")
>>> html.content
'<b> and and &'
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(" and and &",
... content_type="xhtml")
>>> xhtml.content
u' and \xa0 and &'
== 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 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 = """
...
... one
... 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:
=