app/django/utils/feedgenerator.py
changeset 323 ff1a9aa48cfd
parent 54 03e267d67478
--- a/app/django/utils/feedgenerator.py	Tue Oct 14 12:36:55 2008 +0000
+++ b/app/django/utils/feedgenerator.py	Tue Oct 14 16:00:59 2008 +0000
@@ -19,17 +19,29 @@
 http://diveintomark.org/archives/2004/02/04/incompatible-rss
 """
 
+import re
+import datetime
 from django.utils.xmlutils import SimplerXMLGenerator
 from django.utils.encoding import force_unicode, iri_to_uri
-import datetime, re, time
-import email.Utils
 
 def rfc2822_date(date):
-    return email.Utils.formatdate(time.mktime(date.timetuple()))
+    # We do this ourselves to be timezone aware, email.Utils is not tz aware.
+    if date.tzinfo:
+        time_str = date.strftime('%a, %d %b %Y %H:%M:%S ')
+        offset = date.tzinfo.utcoffset(date)
+        timezone = (offset.days * 24 * 60) + (offset.seconds / 60)
+        hour, minute = divmod(timezone, 60)
+        return time_str + "%+03d%02d" % (hour, minute)
+    else:
+        return date.strftime('%a, %d %b %Y %H:%M:%S -0000')
 
 def rfc3339_date(date):
     if date.tzinfo:
-        return date.strftime('%Y-%m-%dT%H:%M:%S%z')
+        time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
+        offset = date.tzinfo.utcoffset(date)
+        timezone = (offset.days * 24 * 60) + (offset.seconds / 60)
+        hour, minute = divmod(timezone, 60)
+        return time_str + "%+03d:%02d" % (hour, minute)
     else:
         return date.strftime('%Y-%m-%dT%H:%M:%SZ')
 
@@ -45,7 +57,7 @@
     "Base class for all syndication feeds. Subclasses should provide write()"
     def __init__(self, title, link, description, language=None, author_email=None,
             author_name=None, author_link=None, subtitle=None, categories=None,
-            feed_url=None, feed_copyright=None, feed_guid=None, ttl=None):
+            feed_url=None, feed_copyright=None, feed_guid=None, ttl=None, **kwargs):
         to_unicode = lambda s: force_unicode(s, strings_only=True)
         if categories:
             categories = [force_unicode(c) for c in categories]
@@ -64,11 +76,13 @@
             'id': feed_guid or link,
             'ttl': ttl,
         }
+        self.feed.update(kwargs)
         self.items = []
 
     def add_item(self, title, link, description, author_email=None,
         author_name=None, author_link=None, pubdate=None, comments=None,
-        unique_id=None, enclosure=None, categories=(), item_copyright=None, ttl=None):
+        unique_id=None, enclosure=None, categories=(), item_copyright=None,
+        ttl=None, **kwargs):
         """
         Adds an item to the feed. All args are expected to be Python Unicode
         objects except pubdate, which is a datetime.datetime object, and
@@ -77,7 +91,7 @@
         to_unicode = lambda s: force_unicode(s, strings_only=True)
         if categories:
             categories = [to_unicode(c) for c in categories]
-        self.items.append({
+        item = {
             'title': to_unicode(title),
             'link': iri_to_uri(link),
             'description': to_unicode(description),
@@ -91,11 +105,39 @@
             'categories': categories or (),
             'item_copyright': to_unicode(item_copyright),
             'ttl': ttl,
-        })
+        }
+        item.update(kwargs)
+        self.items.append(item)
 
     def num_items(self):
         return len(self.items)
 
+    def root_attributes(self):
+        """
+        Return extra attributes to place on the root (i.e. feed/channel) element.
+        Called from write().
+        """
+        return {}
+
+    def add_root_elements(self, handler):
+        """
+        Add elements in the the root (i.e. feed/channel) element. Called
+        from write().
+        """
+        pass
+
+    def item_attributes(self, item):
+        """
+        Return extra attributes to place on each item (i.e. item/entry) element.
+        """
+        return {}
+
+    def add_item_elements(self, handler, item):
+        """
+        Add elements on each item (i.e. item/entry) element.
+        """
+        pass
+
     def write(self, outfile, encoding):
         """
         Outputs the feed in the given encoding to outfile, which is a file-like
@@ -136,8 +178,23 @@
     def write(self, outfile, encoding):
         handler = SimplerXMLGenerator(outfile, encoding)
         handler.startDocument()
-        handler.startElement(u"rss", {u"version": self._version})
-        handler.startElement(u"channel", {})
+        handler.startElement(u"rss", self.rss_attributes())
+        handler.startElement(u"channel", self.root_attributes())
+        self.add_root_elements(handler)
+        self.write_items(handler)
+        self.endChannelElement(handler)
+        handler.endElement(u"rss")
+
+    def rss_attributes(self):
+        return {u"version": self._version}
+
+    def write_items(self, handler):
+        for item in self.items:
+            handler.startElement(u'item', self.item_attributes(item))
+            self.add_item_elements(handler, item)
+            handler.endElement(u"item")
+
+    def add_root_elements(self, handler):
         handler.addQuickElement(u"title", self.feed['title'])
         handler.addQuickElement(u"link", self.feed['link'])
         handler.addQuickElement(u"description", self.feed['description'])
@@ -147,85 +204,84 @@
             handler.addQuickElement(u"category", cat)
         if self.feed['feed_copyright'] is not None:
             handler.addQuickElement(u"copyright", self.feed['feed_copyright'])
-        handler.addQuickElement(u"lastBuildDate", rfc2822_date(self.latest_post_date()).decode('ascii'))
+        handler.addQuickElement(u"lastBuildDate", rfc2822_date(self.latest_post_date()).decode('utf-8'))
         if self.feed['ttl'] is not None:
             handler.addQuickElement(u"ttl", self.feed['ttl'])
-        self.write_items(handler)
-        self.endChannelElement(handler)
-        handler.endElement(u"rss")
 
     def endChannelElement(self, handler):
         handler.endElement(u"channel")
 
 class RssUserland091Feed(RssFeed):
     _version = u"0.91"
-    def write_items(self, handler):
-        for item in self.items:
-            handler.startElement(u"item", {})
-            handler.addQuickElement(u"title", item['title'])
-            handler.addQuickElement(u"link", item['link'])
-            if item['description'] is not None:
-                handler.addQuickElement(u"description", item['description'])
-            handler.endElement(u"item")
+    def add_item_elements(self, handler, item):
+        handler.addQuickElement(u"title", item['title'])
+        handler.addQuickElement(u"link", item['link'])
+        if item['description'] is not None:
+            handler.addQuickElement(u"description", item['description'])
 
 class Rss201rev2Feed(RssFeed):
     # Spec: http://blogs.law.harvard.edu/tech/rss
     _version = u"2.0"
-    def write_items(self, handler):
-        for item in self.items:
-            handler.startElement(u"item", {})
-            handler.addQuickElement(u"title", item['title'])
-            handler.addQuickElement(u"link", item['link'])
-            if item['description'] is not None:
-                handler.addQuickElement(u"description", item['description'])
+    def add_item_elements(self, handler, item):
+        handler.addQuickElement(u"title", item['title'])
+        handler.addQuickElement(u"link", item['link'])
+        if item['description'] is not None:
+            handler.addQuickElement(u"description", item['description'])
 
-            # Author information.
-            if item["author_name"] and item["author_email"]:
-                handler.addQuickElement(u"author", "%s (%s)" % \
-                    (item['author_email'], item['author_name']))
-            elif item["author_email"]:
-                handler.addQuickElement(u"author", item["author_email"])
-            elif item["author_name"]:
-                handler.addQuickElement(u"dc:creator", item["author_name"], {"xmlns:dc": u"http://purl.org/dc/elements/1.1/"})
+        # Author information.
+        if item["author_name"] and item["author_email"]:
+            handler.addQuickElement(u"author", "%s (%s)" % \
+                (item['author_email'], item['author_name']))
+        elif item["author_email"]:
+            handler.addQuickElement(u"author", item["author_email"])
+        elif item["author_name"]:
+            handler.addQuickElement(u"dc:creator", item["author_name"], {"xmlns:dc": u"http://purl.org/dc/elements/1.1/"})
 
-            if item['pubdate'] is not None:
-                handler.addQuickElement(u"pubDate", rfc2822_date(item['pubdate']).decode('ascii'))
-            if item['comments'] is not None:
-                handler.addQuickElement(u"comments", item['comments'])
-            if item['unique_id'] is not None:
-                handler.addQuickElement(u"guid", item['unique_id'])
-            if item['ttl'] is not None:
-                handler.addQuickElement(u"ttl", item['ttl'])
+        if item['pubdate'] is not None:
+            handler.addQuickElement(u"pubDate", rfc2822_date(item['pubdate']).decode('utf-8'))
+        if item['comments'] is not None:
+            handler.addQuickElement(u"comments", item['comments'])
+        if item['unique_id'] is not None:
+            handler.addQuickElement(u"guid", item['unique_id'])
+        if item['ttl'] is not None:
+            handler.addQuickElement(u"ttl", item['ttl'])
 
-            # Enclosure.
-            if item['enclosure'] is not None:
-                handler.addQuickElement(u"enclosure", '',
-                    {u"url": item['enclosure'].url, u"length": item['enclosure'].length,
-                        u"type": item['enclosure'].mime_type})
+        # Enclosure.
+        if item['enclosure'] is not None:
+            handler.addQuickElement(u"enclosure", '',
+                {u"url": item['enclosure'].url, u"length": item['enclosure'].length,
+                    u"type": item['enclosure'].mime_type})
 
-            # Categories.
-            for cat in item['categories']:
-                handler.addQuickElement(u"category", cat)
-
-            handler.endElement(u"item")
+        # Categories.
+        for cat in item['categories']:
+            handler.addQuickElement(u"category", cat)
 
 class Atom1Feed(SyndicationFeed):
     # Spec: http://atompub.org/2005/07/11/draft-ietf-atompub-format-10.html
     mime_type = 'application/atom+xml'
     ns = u"http://www.w3.org/2005/Atom"
+
     def write(self, outfile, encoding):
         handler = SimplerXMLGenerator(outfile, encoding)
         handler.startDocument()
+        handler.startElement(u'feed', self.root_attributes())
+        self.add_root_elements(handler)
+        self.write_items(handler)
+        handler.endElement(u"feed")
+
+    def root_attributes(self):
         if self.feed['language'] is not None:
-            handler.startElement(u"feed", {u"xmlns": self.ns, u"xml:lang": self.feed['language']})
+            return {u"xmlns": self.ns, u"xml:lang": self.feed['language']}
         else:
-            handler.startElement(u"feed", {u"xmlns": self.ns})
+            return {u"xmlns": self.ns}
+
+    def add_root_elements(self, handler):
         handler.addQuickElement(u"title", self.feed['title'])
         handler.addQuickElement(u"link", "", {u"rel": u"alternate", u"href": self.feed['link']})
         if self.feed['feed_url'] is not None:
             handler.addQuickElement(u"link", "", {u"rel": u"self", u"href": self.feed['feed_url']})
         handler.addQuickElement(u"id", self.feed['id'])
-        handler.addQuickElement(u"updated", rfc3339_date(self.latest_post_date()).decode('ascii'))
+        handler.addQuickElement(u"updated", rfc3339_date(self.latest_post_date()).decode('utf-8'))
         if self.feed['author_name'] is not None:
             handler.startElement(u"author", {})
             handler.addQuickElement(u"name", self.feed['author_name'])
@@ -240,55 +296,55 @@
             handler.addQuickElement(u"category", "", {u"term": cat})
         if self.feed['feed_copyright'] is not None:
             handler.addQuickElement(u"rights", self.feed['feed_copyright'])
-        self.write_items(handler)
-        handler.endElement(u"feed")
 
     def write_items(self, handler):
         for item in self.items:
-            handler.startElement(u"entry", {})
-            handler.addQuickElement(u"title", item['title'])
-            handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"alternate"})
-            if item['pubdate'] is not None:
-                handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('ascii'))
+            handler.startElement(u"entry", self.item_attributes(item))
+            self.add_item_elements(handler, item)
+            handler.endElement(u"entry")
+
+    def add_item_elements(self, handler, item):
+        handler.addQuickElement(u"title", item['title'])
+        handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"alternate"})
+        if item['pubdate'] is not None:
+            handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('utf-8'))
 
-            # Author information.
-            if item['author_name'] is not None:
-                handler.startElement(u"author", {})
-                handler.addQuickElement(u"name", item['author_name'])
-                if item['author_email'] is not None:
-                    handler.addQuickElement(u"email", item['author_email'])
-                if item['author_link'] is not None:
-                    handler.addQuickElement(u"uri", item['author_link'])
-                handler.endElement(u"author")
+        # Author information.
+        if item['author_name'] is not None:
+            handler.startElement(u"author", {})
+            handler.addQuickElement(u"name", item['author_name'])
+            if item['author_email'] is not None:
+                handler.addQuickElement(u"email", item['author_email'])
+            if item['author_link'] is not None:
+                handler.addQuickElement(u"uri", item['author_link'])
+            handler.endElement(u"author")
 
-            # Unique ID.
-            if item['unique_id'] is not None:
-                unique_id = item['unique_id']
-            else:
-                unique_id = get_tag_uri(item['link'], item['pubdate'])
-            handler.addQuickElement(u"id", unique_id)
+        # Unique ID.
+        if item['unique_id'] is not None:
+            unique_id = item['unique_id']
+        else:
+            unique_id = get_tag_uri(item['link'], item['pubdate'])
+        handler.addQuickElement(u"id", unique_id)
 
-            # Summary.
-            if item['description'] is not None:
-                handler.addQuickElement(u"summary", item['description'], {u"type": u"html"})
+        # Summary.
+        if item['description'] is not None:
+            handler.addQuickElement(u"summary", item['description'], {u"type": u"html"})
 
-            # Enclosure.
-            if item['enclosure'] is not None:
-                handler.addQuickElement(u"link", '',
-                    {u"rel": u"enclosure",
-                     u"href": item['enclosure'].url,
-                     u"length": item['enclosure'].length,
-                     u"type": item['enclosure'].mime_type})
+        # Enclosure.
+        if item['enclosure'] is not None:
+            handler.addQuickElement(u"link", '',
+                {u"rel": u"enclosure",
+                 u"href": item['enclosure'].url,
+                 u"length": item['enclosure'].length,
+                 u"type": item['enclosure'].mime_type})
 
-            # Categories.
-            for cat in item['categories']:
-                handler.addQuickElement(u"category", u"", {u"term": cat})
+        # Categories.
+        for cat in item['categories']:
+            handler.addQuickElement(u"category", u"", {u"term": cat})
 
-            # Rights.
-            if item['item_copyright'] is not None:
-                handler.addQuickElement(u"rights", item['item_copyright'])
-
-            handler.endElement(u"entry")
+        # Rights.
+        if item['item_copyright'] is not None:
+            handler.addQuickElement(u"rights", item['item_copyright'])
 
 # This isolates the decision of what the system default is, so calling code can
 # do "feedgenerator.DefaultFeed" instead of "feedgenerator.Rss201rev2Feed".