diff options
| author | Adrian Holovaty <adrian@holovaty.com> | 2005-11-12 03:44:53 +0000 |
|---|---|---|
| committer | Adrian Holovaty <adrian@holovaty.com> | 2005-11-12 03:44:53 +0000 |
| commit | 944de9e9e638bc239b03f67f459ad4abe4673e48 (patch) | |
| tree | d144b7c68fc2f0a7169aa07d0a91daa246b94473 /django/utils | |
| parent | e8ae3567393443934d896579dd6d1dfc92bddb95 (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.py | 219 |
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 |
