turbofeeds.controllers

Package turbofeeds :: Module controllers |
[hide private]

# Source Code for Module turbofeeds.controllers

      1  # -*- coding: UTF-8 -*-
      2  """TurboFeed controllers for RSS/Atom feeds handling."""
      3  __docformat__ = 'restructuredtext'
      4
      5  __all__ = [
      6      'FeedController',
      7  ]
      8
      9  __version__ = '1.1'
     10
     11  import logging
     12
     13  import turbogears
     14
     15  from util import xml_stylesheet, absolute_url
     16
     17
     18  log = logging.getLogger('turbofeeds.controllers')
     19
     20
     21 -class FeedController(turbogears.controllers.Controller):
     22      """Controller for generating feeds in multiple formats.
     23
     24      Must be subclassed and a ``get_feed_data`` method provided that returns
     25      a dict with the feed info (see constructor doc strings) and an ``entries``
     26      member, which should be a list filled with dicts each representing a feed
     27      entry (see docstring for the default implementation).
     28      """
     29
     30      formats = ["atom1.0", "atom0.3", "rss2.0"]
     31
     32      # Constructor / Intialisation
     33 -    def __init__(self, default="atom1.0", base_url='/feed', **feed_params):
     34          """Constructor - Should be called with ``super()`` if overwritten.
     35
     36          The ``default`` arguments sets the default feed format when the
     37          feed base URL is requested. Currently supported values are
     38          ``atom1_0`` (the default), ``atom0_3`` and ''rss1_0``.
     39
     40          The ``base_url`` sets the URL where the feed controller is mounted
     41          to the CherryPy object tree. The default is '/feed'. This is used
     42          to construct the full feed URL for the ``link`` element in the feed,
     43          if it is not overwritten by the ``link`` keyword argument.
     44
     45          Any extra keyword arguments will be assigned to the ``feed_params``
     46          attribute and added to the feed data everytime a feed is requested.
     47          This can be used to set the feed info in the direct child elements
     48          of the ``feed`` (Atom) resp. ``channel`` root element of the feed.
     49          Possible names include:
     50
     51          * author (dict with ``name``, ``email`` and ``uri`` members)
     52          * categories (list of strings, used by Atom 1.0 / RSS only)
     53          * generator (string)
     54          * updated (datetime)
     55          * icon (URL, used by Atom 1.0 format only)
     56          * id (string/URL, used by Atom formats only)
     57          * logo (URL, used by Atom 1.0 / RSS format only)
     58          * lang (string, used by RSS format only)
     59          * link (URL)
     60          * rights (string)
     61          * subtitle (string)
     62          * stylesheet (URL or dict with members ``href`` and ``type`` or a
     63            calable returning either, used place a matching xml-stylesheet
     64            processing instruction at the top of the feed XML)
     65          * title (string)
     66
     67          For up-to-date information about supported elements and values, please
     68          refer to the templates for the different feed formats in the
     69          ``templates`` sub-package.
     70          """
     71
     72          super(FeedController, self).__init__()
     73          assert default in self.formats
     74          self.default = default
     75          self.base_url = base_url
     76          self.feed_params = feed_params
     77
     78      # Exposed methods
     79      @turbogears.expose()
     80 -    def index(self, *args, **kwargs):
     81          """Redirects to the default feed format rendering method."""
     82
     83          turbogears.redirect(turbogears.url("%s" % self.default), kwargs)
     84
     85      @turbogears.expose(template="turbofeeds.templates.atom0_3",
     86          format="xml", content_type="application/atom+xml")
     87 -    def atom0_3(self, *args, **kwargs):
     88          """Renders Atom 0.3 XML feed."""
     89
     90          feed = self.init_feed()
     91          feed.update(self.get_feed_data(*args, **kwargs))
     92          self.format_dates(feed, 3339)
     93          feed.setdefault('link', self.get_feed_url("atom0_3", *args, **kwargs))
     94          log.debug(feed)
     95          return feed
     96
     97      @turbogears.expose(template="turbofeeds.templates.atom1_0",
     98          format="xml", content_type="application/atom+xml")
     99 -    def atom1_0(self, *args, **kwargs):
    100          """Renders Atom 1.0 XML feed."""
    101
    102          feed = self.init_feed()
    103          feed.update(self.get_feed_data(*args, **kwargs))
    104          self.format_dates(feed, 3339)
    105          feed.setdefault('link', self.get_feed_url("atom1_0", *args, **kwargs))
    106          log.debug(feed)
    107          return feed
    108
    109      @turbogears.expose(template="turbofeeds.templates.rss2_0",
    110          format="xml", content_type="application/rss+xml")
    111 -    def rss2_0(self, *args, **kwargs):
    112          """Renders RSS 2.0 XML feed."""
    113
    114          feed = self.init_feed()
    115          feed.update(self.get_feed_data(*args, **kwargs))
    116          self.format_dates(feed, 822)
    117          feed.setdefault('link', self.get_feed_url("rss2_0", *args, **kwargs))
    118          log.debug(feed)
    119          return feed
    120
    121      # Helper methods
    122 -    def date_to_3339(self, date):
    123          """Converts datatime object to RFC 3339 string representation."""
    124
    125          date = date.strftime("%Y-%m-%dT%H:%M:%SZ")
    126          return date
    127
    128 -    def date_to_822(self, date):
    129          """Converts datatime object to RFC 822 string representation."""
    130
    131          date = date.strftime("%a, %d %b %Y %H:%M:%S GMT")
    132          return date
    133
    134 -    def format_dates(self, feed, format):
    135          """Converts datetime objects in the feed data into given format."""
    136
    137          if format == 822:
    138              convert_date = self.date_to_822
    139          else:
    140              convert_date = self.date_to_3339
    141          for field in ('published', 'updated'):
    142              if field in feed:
    143                  feed[field] = convert_date(feed[field])
    144
    145          for entry in feed['entries']:
    146              for field in ('published', 'updated'):
    147                  if field in entry:
    148                      entry[field] = convert_date(entry[field])
    149          return feed
    150
    151 -    def get_feed_data(self, *args, **kwargs):
    152          """Returns dict with feed info and a list of feed entries.
    153
    154          This method must be overwritten by a subclass.
    155
    156          It should return a dictionary with members for the feed info (see the
    157          constructor doc string) and a member ``entries``, which is a list with
    158          dict items for each feed entry. Supported members in each dict include:
    159
    160          * author (dict with ``name``, ``email`` and ``uri`` members)
    161          * categories (list of strings, used by Atom 1.0 / RSS only)
    162          * content (string or dict with ``type`` and ``value`` members,
    163            Atom formats only)
    164          * updated (datetime, Atom only)
    165          * id (string/URL, Atom only)
    166          * link (URL)
    167          * published (datetime)
    168          * rights (string, Atom 1.0 only)
    169          * summary (string)
    170          * title (string)
    171
    172          For up-to-date information about supported elements and values, please
    173          refer to the templates for the different feed formats in the
    174          ``templates`` sub-package.
    175          """
    176
    177          raise NotImplementedError
    178
    179 -    def get_feed_url(self, version='atom1_0', *args, **kwargs):
    180          """Returns absolute URL (including server name) for the feed."""
    181
    182          if not self.base_url.endswith('/'):
    183              base_url = self.base_url + '/'
    184          else:
    185              base_url = self.base_url + '/'
    186          if args:
    187              suffix = '/' + '/'.join([str(a) for a in args])
    188          else:
    189              suffix = ''
    190          feed_url = absolute_url('%s%s%s' %
    191            (base_url, version, suffix), **kwargs)
    192          return feed_url
    193
    194 -    def init_feed(self):
    195          """Initializes feed data with the kwargs given to the constructor."""
    196
    197          feed = {
    198              'xml_stylesheet': xml_stylesheet,
    199              'generator': 'TurboFeeds FeedController version %s' % __version__
    200          }
    201          feed.update(self.feed_params)
    202          return feed
    203