summaryrefslogtreecommitdiff
path: root/django/utils
diff options
context:
space:
mode:
authorAdrian Holovaty <adrian@holovaty.com>2005-11-12 03:44:53 +0000
committerAdrian Holovaty <adrian@holovaty.com>2005-11-12 03:44:53 +0000
commit944de9e9e638bc239b03f67f459ad4abe4673e48 (patch)
treed144b7c68fc2f0a7169aa07d0a91daa246b94473 /django/utils
parente8ae3567393443934d896579dd6d1dfc92bddb95 (diff)
Completely refactored legacy RSS framework to the new django.contrib.syndication package. Also added Atom support, changed the way feeds are registered and added documentation for the whole lot. This is backwards-incompatible, but the RSS framework had not yet been documented, so this should only affect tinkerers and WorldOnline. Fixes #329, #498, #502 and #554. Thanks for various patches/ideas to alastair, ismael, hugo, eric moritz and garthk
git-svn-id: http://code.djangoproject.com/svn/django/trunk@1194 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/utils')
-rw-r--r--django/utils/feedgenerator.py219
1 files changed, 158 insertions, 61 deletions
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py
index e8e58718ff..d482685383 100644
--- a/django/utils/feedgenerator.py
+++ b/django/utils/feedgenerator.py
@@ -19,21 +19,42 @@ http://diveintomark.org/archives/2004/02/04/incompatible-rss
"""
from django.utils.xmlutils import SimplerXMLGenerator
+import datetime, re, time
+import email.Utils
+from xml.dom import minidom
+from xml.parsers.expat import ExpatError
+
+def rfc2822_date(date):
+ return email.Utils.formatdate(time.mktime(date.timetuple()))
+
+def get_tag_uri(url, date):
+ "Creates a TagURI. See http://diveintomark.org/archives/2004/05/28/howto-atom-id"
+ tag = re.sub('^http://', '', url)
+ if date is not None:
+ tag = re.sub('/', ',%s:/' % date.strftime('%Y-%m-%d'), tag, 1)
+ tag = re.sub('#', '/', tag)
+ return 'tag:' + tag
class SyndicationFeed:
"Base class for all syndication feeds. Subclasses should provide write()"
- def __init__(self, title, link, description, language=None):
- self.feed_info = {
+ def __init__(self, title, link, description, language=None, author_email=None,
+ author_name=None, author_link=None, subtitle=None, categories=None):
+ self.feed = {
'title': title,
'link': link,
'description': description,
'language': language,
+ 'author_email': author_email,
+ 'author_name': author_name,
+ 'author_link': author_link,
+ 'subtitle': subtitle,
+ 'categories': categories or (),
}
self.items = []
def add_item(self, title, link, description, author_email=None,
- author_name=None, pubdate=None, comments=None, unique_id=None,
- enclosure=None, categories=None):
+ author_name=None, pubdate=None, comments=None,
+ unique_id=None, enclosure=None, categories=()):
"""
Adds an item to the feed. All args are expected to be Python Unicode
objects except pubdate, which is a datetime.datetime object, and
@@ -49,7 +70,7 @@ class SyndicationFeed:
'comments': comments,
'unique_id': unique_id,
'enclosure': enclosure,
- 'categories': categories or [],
+ 'categories': categories or (),
})
def num_items(self):
@@ -71,6 +92,18 @@ class SyndicationFeed:
self.write(s, encoding)
return s.getvalue()
+ def latest_post_date(self):
+ """
+ Returns the latest item's pubdate. If none of them have a pubdate,
+ this returns the current date/time.
+ """
+ updates = [i['pubdate'] for i in self.items if i['pubdate'] is not None]
+ if len(updates) > 0:
+ updates.sort()
+ return updates[-1]
+ else:
+ return datetime.datetime.now()
+
class Enclosure:
"Represents an RSS enclosure"
def __init__(self, url, length, mime_type):
@@ -81,72 +114,136 @@ class RssFeed(SyndicationFeed):
def write(self, outfile, encoding):
handler = SimplerXMLGenerator(outfile, encoding)
handler.startDocument()
- self.writeRssElement(handler)
- self.writeChannelElement(handler)
- for item in self.items:
- self.writeRssItem(handler, item)
+ handler.startElement(u"rss", {u"version": self._version})
+ handler.startElement(u"channel", {})
+ handler.addQuickElement(u"title", self.feed['title'])
+ handler.addQuickElement(u"link", self.feed['link'])
+ handler.addQuickElement(u"description", self.feed['description'])
+ if self.feed['language'] is not None:
+ handler.addQuickElement(u"language", self.feed['language'])
+ self.write_items(handler)
self.endChannelElement(handler)
- self.endRssElement(handler)
-
- def writeRssElement(self, handler):
- "Adds the <rss> element to handler, taking care of versioning, etc."
- raise NotImplementedError
-
- def endRssElement(self, handler):
- "Ends the <rss> element."
handler.endElement(u"rss")
- def writeChannelElement(self, handler):
- handler.startElement(u"channel", {})
- handler.addQuickElement(u"title", self.feed_info['title'], {})
- handler.addQuickElement(u"link", self.feed_info['link'], {})
- handler.addQuickElement(u"description", self.feed_info['description'], {})
- if self.feed_info['language'] is not None:
- handler.addQuickElement(u"language", self.feed_info['language'], {})
-
def endChannelElement(self, handler):
handler.endElement(u"channel")
class RssUserland091Feed(RssFeed):
- def writeRssElement(self, handler):
- handler.startElement(u"rss", {u"version": u"0.91"})
-
- def writeRssItem(self, handler, item):
- 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")
+ _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")
class Rss201rev2Feed(RssFeed):
# Spec: http://blogs.law.harvard.edu/tech/rss
- def writeRssElement(self, handler):
- handler.startElement(u"rss", {u"version": u"2.0"})
+ _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'])
+
+ # Author information.
+ if item['author_email'] is not None and item['author_name'] is not None:
+ handler.addQuickElement(u"author", u"%s (%s)" % \
+ (item['author_email'], item['author_name']))
+
+ 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'])
+
+ # 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")
+
+class Atom1Feed(SyndicationFeed):
+ # Spec: http://atompub.org/2005/07/11/draft-ietf-atompub-format-10.html
+ ns = u"http://www.w3.org/2005/Atom"
+ def write(self, outfile, encoding):
+ handler = SimplerXMLGenerator(outfile, encoding)
+ handler.startDocument()
+ if self.feed['language'] is not None:
+ handler.startElement(u"feed", {u"xmlns": self.ns, u"xml:lang": self.feed['language']})
+ else:
+ handler.startElement(u"feed", {u"xmlns": self.ns})
+ handler.addQuickElement(u"title", self.feed['title'])
+ handler.addQuickElement(u"link", "", {u"href": self.feed['link']})
+ handler.addQuickElement(u"id", self.feed['link'])
+ handler.addQuickElement(u"updated", rfc2822_date(self.latest_post_date()).decode('ascii'))
+ if self.feed['author_name'] is not None:
+ handler.startElement(u"author", {})
+ handler.addQuickElement(u"name", self.feed['author_name'])
+ if self.feed['author_email'] is not None:
+ handler.addQuickElement(u"email", self.feed['author_email'])
+ if self.feed['author_link'] is not None:
+ handler.addQuickElement(u"uri", self.feed['author_link'])
+ handler.endElement(u"author")
+ if self.feed['subtitle'] is not None:
+ handler.addQuickElement(u"subtitle", self.feed['subtitle'])
+ for cat in self.feed['categories']:
+ handler.addQuickElement(u"category", "", {u"term": cat})
+ 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", item['link'])
+ if item['pubdate'] is not None:
+ handler.addQuickElement(u"updated", rfc2822_date(item['pubdate']).decode('ascii'))
+
+ # 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'])
+ 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)
+
+ # 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})
+
+ # Categories:
+ for cat in item['categories']:
+ handler.addQuickElement(u"category", u"", {u"term": cat})
- def writeRssItem(self, handler, item):
- 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'], {})
- if item['author_email'] is not None and item['author_name'] is not None:
- handler.addQuickElement(u"author", u"%s (%s)" % \
- (item['author_email'], item['author_name']), {})
- if item['pubdate'] is not None:
- handler.addQuickElement(u"pubDate", item['pubdate'].strftime('%a, %d %b %Y %H:%M:%S %Z'), {})
- 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['enclosure'] is not None:
- handler.addQuickElement(u"enclosure", '',
- {u"url": item['enclosure'].url, u"length": item['enclosure'].length,
- u"type": item['enclosure'].mime_type})
- for cat in item['categories']:
- handler.addQuickElement(u"category", cat, {})
- handler.endElement(u"item")
+ handler.endElement(u"entry")
# This isolates the decision of what the system default is, so calling code can
-# do "feedgenerator.DefaultRssFeed" instead of "feedgenerator.Rss201rev2Feed".
-DefaultRssFeed = Rss201rev2Feed
+# do "feedgenerator.DefaultFeed" instead of "feedgenerator.Rss201rev2Feed".
+DefaultFeed = Rss201rev2Feed