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  import logging 
 10  from functools import partial 
 11   
 12  import turbogears as tg 
 13   
 14  from release import version as __version__ 
 15  from util import xml_stylesheet, absolute_url 
 16   
 17   
 18  log = logging.getLogger('turbofeeds.controllers') 
19 20 21 -class FeedController(tg.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", "mrss1.1.1"] 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``, ``rss2_0`` and ``mrss1_1_1``. 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`` (RSS) root element of the 49 feed. 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 callable returning either, used to place an appropriate 64 xml-stylesheet processing instruction at the top of the feed XML. 65 The stylesheet function will receive the format of the feed as the 66 first argument and all extra keyword arguments to this constructor 67 as keword arguments as well.) 68 * title (string) 69 70 For up-to-date information about supported elements and values, please 71 refer to the templates for the different feed formats in the 72 ``templates`` sub-package. 73 74 """ 75 super(FeedController, self).__init__() 76 if not default in self.formats: 77 raise ValueError("Default format '%s' is not supported" % default) 78 self.default = default 79 self.base_url = base_url 80 self.feed_params = feed_params 81 log.info("TurboFeeds FeedController (%s) initialised", __version__) 82 log.debug("FeedController base_url: %s", base_url)
83 84 # Exposed methods 85 @tg.expose()
86 - def index(self, *args, **kwargs):
87 """Redirects to the default feed format rendering method.""" 88 89 tg.redirect(self.default, kwargs)
90 91 @tg.expose(template="turbofeeds.templates.atom0_3", 92 format="xml", content_type="application/atom+xml")
93 - def atom0_3(self, *args, **kwargs):
94 """Renders Atom 0.3 XML feed.""" 95 96 feed = self.init_feed("atom0_3") 97 feed.update(self.get_feed_data(*args, **kwargs)) 98 self.format_dates(feed, 3339) 99 feed.setdefault('link', self.get_feed_url("atom0_3", *args, **kwargs)) 100 log.debug(feed) 101 return feed
102 103 @tg.expose(template="turbofeeds.templates.atom1_0", 104 format="xml", content_type="application/atom+xml")
105 - def atom1_0(self, *args, **kwargs):
106 """Renders Atom 1.0 XML feed.""" 107 108 feed = self.init_feed("atom1_0") 109 feed.update(self.get_feed_data(*args, **kwargs)) 110 self.format_dates(feed, 3339) 111 feed.setdefault('link', self.get_feed_url("atom1_0", *args, **kwargs)) 112 log.debug(feed) 113 return feed
114 115 @tg.expose(template="turbofeeds.templates.rss2_0", 116 format="xml", content_type="application/rss+xml")
117 - def rss2_0(self, *args, **kwargs):
118 """Renders RSS 2.0 XML feed.""" 119 120 feed = self.init_feed("rss2_0") 121 feed.update(self.get_feed_data(*args, **kwargs)) 122 self.format_dates(feed, 822) 123 feed.setdefault('link', self.get_feed_url("rss2_0", *args, **kwargs)) 124 log.debug(feed) 125 return feed
126 127 @tg.expose(template="turbofeeds.templates.mrss1_1_1", 128 format="xml", content_type="application/rss+xml")
129 - def mrss1_1_1(self, *args, **kwargs):
130 """Renders Media RSS 1.1.1 (extended from RSS 2.0) XML feed.""" 131 132 feed = self.init_feed("mrss1_1_1") 133 feed.update(self.get_feed_data(*args, **kwargs)) 134 self.format_dates(feed, 822) 135 feed.setdefault('link', 136 self.get_feed_url("mrss_1_1_1", *args, **kwargs)) 137 log.debug(feed) 138 return feed
139 140 # Helper methods
141 - def date_to_3339(self, date):
142 """Converts datatime object to RFC 3339 string representation.""" 143 144 date = date.strftime("%Y-%m-%dT%H:%M:%SZ") 145 return date
146
147 - def date_to_822(self, date):
148 """Converts datatime object to RFC 822 string representation.""" 149 150 date = date.strftime("%a, %d %b %Y %H:%M:%S GMT") 151 return date
152
153 - def format_dates(self, feed, format):
154 """Converts datetime objects in the feed data into given format.""" 155 156 if format == 822: 157 convert_date = self.date_to_822 158 else: 159 convert_date = self.date_to_3339 160 for field in ('published', 'updated'): 161 if field in feed: 162 feed[field] = convert_date(feed[field]) 163 164 for entry in feed['entries']: 165 for field in ('published', 'updated'): 166 if field in entry: 167 entry[field] = convert_date(entry[field]) 168 return feed
169
170 - def get_feed_data(self, *args, **kwargs):
171 """Returns dict with feed info and a list of feed entries. 172 173 This method must be overwritten by a subclass. 174 175 It should return a dictionary with members for the feed info (see the 176 constructor doc string) and a member ``entries``, which is a list with 177 dict items for each feed entry. Supported members in each dict include: 178 179 * author (dict with ``name``, ``email`` and ``uri`` members) 180 * categories (list of strings, used by Atom 1.0 / RSS only) 181 * content (string or dict with ``type`` and ``value`` members, 182 Atom formats only) 183 * updated (datetime, Atom only) 184 * id (string/URL, Atom only) 185 * link (URL) 186 * published (datetime) 187 * rights (string, Atom 1.0 only) 188 * summary (string) 189 * title (string) 190 191 For up-to-date information about supported elements and values, please 192 refer to the templates for the different feed formats in the 193 ``templates`` sub-package. 194 195 """ 196 raise NotImplementedError( 197 "This method must be overwritten by a subclass.")
198
199 - def get_feed_url(self, version='atom1_0', *args, **kwargs):
200 """Returns absolute URL (including server name) for the feed.""" 201 202 if not self.base_url.endswith('/'): 203 base_url = self.base_url + '/' 204 else: 205 base_url = self.base_url 206 if args: 207 suffix = '/' + '/'.join([str(a) for a in args]) 208 else: 209 suffix = '' 210 return absolute_url([base_url, version, suffix], **kwargs)
211
212 - def init_feed(self, format):
213 """Initializes feed data with the kwargs given to the constructor.""" 214 215 engine = self.feed_params.get('engine', 216 tg.config.get('tg.defaultview', 'text')) 217 feed = { 218 'xml_stylesheet': partial(xml_stylesheet, engine=engine), 219 'generator': 'TurboFeeds FeedController (%s, TG %s, %s)' % ( 220 __version__, tg.__version__, engine) 221 } 222 feed.update(self.feed_params) 223 if 'stylesheet' in feed and callable(feed['stylesheet']): 224 func = feed.pop('stylesheet') 225 feed['stylesheet'] = func(format, **feed) 226 log.debug("Using stylesheet: %r", feed['stylesheet']) 227 return feed
228