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
This commit is contained in:
parent
e8ae356739
commit
944de9e9e6
|
@ -1,6 +0,0 @@
|
|||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('django.views',
|
||||
(r'^(?P<slug>[^/]+)/$', 'rss.rss.feed'),
|
||||
(r'^(?P<slug>[^/]+)/(?P<param>.+)/$', 'rss.rss.feed'),
|
||||
)
|
|
@ -0,0 +1,89 @@
|
|||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
||||
from django.core.template import Context, loader, Template, TemplateDoesNotExist
|
||||
from django.models.core import sites
|
||||
from django.utils import feedgenerator
|
||||
from django.conf.settings import LANGUAGE_CODE, SETTINGS_MODULE
|
||||
|
||||
def add_domain(domain, url):
|
||||
if not url.startswith('http://'):
|
||||
url = u'http://%s%s' % (domain, url)
|
||||
return url
|
||||
|
||||
class FeedDoesNotExist(ObjectDoesNotExist):
|
||||
pass
|
||||
|
||||
class Feed:
|
||||
item_pubdate = None
|
||||
item_enclosure_url = None
|
||||
feed_type = feedgenerator.DefaultFeed
|
||||
|
||||
def __init__(self, slug):
|
||||
self.slug = slug
|
||||
|
||||
def item_link(self, item):
|
||||
try:
|
||||
return item.get_absolute_url()
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured, "Give your %s class a get_absolute_url() method, or define an item_link() method in your Feed class." % item.__class__.__name__
|
||||
|
||||
def __get_dynamic_attr(self, attname, obj):
|
||||
attr = getattr(self, attname)
|
||||
if callable(attr):
|
||||
try:
|
||||
return attr(obj)
|
||||
except TypeError:
|
||||
return attr()
|
||||
return attr
|
||||
|
||||
def get_feed(self, url=None):
|
||||
"""
|
||||
Returns a feedgenerator.DefaultFeed object, fully populated, for
|
||||
this feed. Raises FeedDoesNotExist for invalid parameters.
|
||||
"""
|
||||
if url:
|
||||
try:
|
||||
obj = self.get_object(url.split('/'))
|
||||
except (AttributeError, ObjectDoesNotExist):
|
||||
raise FeedDoesNotExist
|
||||
else:
|
||||
obj = None
|
||||
|
||||
current_site = sites.get_current()
|
||||
link = self.__get_dynamic_attr('link', obj)
|
||||
link = add_domain(current_site.domain, link)
|
||||
|
||||
feed = self.feed_type(
|
||||
title = self.__get_dynamic_attr('title', obj),
|
||||
link = link,
|
||||
description = self.__get_dynamic_attr('description', obj),
|
||||
language = LANGUAGE_CODE.decode()
|
||||
)
|
||||
|
||||
try:
|
||||
title_template = loader.get_template('feeds/%s_title' % self.slug)
|
||||
except TemplateDoesNotExist:
|
||||
title_template = Template('{{ obj }}')
|
||||
try:
|
||||
description_template = loader.get_template('feeds/%s_description' % self.slug)
|
||||
except TemplateDoesNotExist:
|
||||
description_template = Template('{{ obj }}')
|
||||
|
||||
for item in self.__get_dynamic_attr('items', obj):
|
||||
link = add_domain(current_site.domain, self.__get_dynamic_attr('item_link', item))
|
||||
enc = None
|
||||
enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
|
||||
if enc_url:
|
||||
enc = feedgenerator.Enclosure(
|
||||
url = enc_url.decode('utf-8'),
|
||||
length = str(self.__get_dynamic_attr('item_enclosure_length', item)).decode('utf-8'),
|
||||
mime_type = self.__get_dynamic_attr('item_enclosure_mime_type', item).decode('utf-8'),
|
||||
)
|
||||
feed.add_item(
|
||||
title = title_template.render(Context({'obj': item, 'site': current_site})).decode('utf-8'),
|
||||
link = link,
|
||||
description = description_template.render(Context({'obj': item, 'site': current_site})).decode('utf-8'),
|
||||
unique_id = link,
|
||||
enclosure = enc,
|
||||
pubdate = self.__get_dynamic_attr('item_pubdate', item),
|
||||
)
|
||||
return feed
|
|
@ -0,0 +1,26 @@
|
|||
from django.contrib.syndication import feeds
|
||||
from django.core.exceptions import Http404
|
||||
from django.utils.httpwrappers import HttpResponse
|
||||
|
||||
def feed(request, url, feed_dict=None):
|
||||
if not feed_dict:
|
||||
raise Http404, "No feeds are registered."
|
||||
|
||||
try:
|
||||
slug, param = url.split('/', 1)
|
||||
except ValueError:
|
||||
slug, param = url, ''
|
||||
|
||||
try:
|
||||
f = feed_dict[slug]
|
||||
except KeyError:
|
||||
raise Http404, "Slug %r isn't registered." % slug
|
||||
|
||||
try:
|
||||
feedgen = f(slug).get_feed(param)
|
||||
except feeds.FeedDoesNotExist:
|
||||
raise Http404, "Invalid feed parameters. Slug %r is valid, but other parameters, or lack thereof, are not." % slug
|
||||
|
||||
response = HttpResponse(mimetype='application/xml')
|
||||
feedgen.write(response, 'utf-8')
|
||||
return response
|
|
@ -1,223 +0,0 @@
|
|||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
||||
from django.core.template import Context, loader, Template, TemplateDoesNotExist
|
||||
from django.models.core import sites
|
||||
from django.utils import feedgenerator
|
||||
from django.conf.settings import LANGUAGE_CODE, SETTINGS_MODULE
|
||||
|
||||
def add_domain(domain, url):
|
||||
if not url.startswith('http://'):
|
||||
url = u'http://%s%s' % (domain, url)
|
||||
return url
|
||||
|
||||
class FeedDoesNotExist(ObjectDoesNotExist):
|
||||
pass
|
||||
|
||||
class Feed:
|
||||
item_pubdate = None
|
||||
item_enclosure_url = None
|
||||
|
||||
def item_link(self, item):
|
||||
try:
|
||||
return item.get_absolute_url()
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured, "Give your %s class a get_absolute_url() method, or define an item_link() method in your RSS class." % item.__class__.__name__
|
||||
|
||||
def __get_dynamic_attr(self, attname, obj):
|
||||
attr = getattr(self, attname)
|
||||
if callable(attr):
|
||||
try:
|
||||
return attr(obj)
|
||||
except TypeError:
|
||||
return attr()
|
||||
return attr
|
||||
|
||||
def get_feed(self, url=None):
|
||||
"""
|
||||
Returns a feedgenerator.DefaultRssFeed object, fully populated, for
|
||||
this feed. Raises FeedDoesNotExist for invalid parameters.
|
||||
"""
|
||||
if url:
|
||||
try:
|
||||
obj = self.get_object(url.split('/'))
|
||||
except (AttributeError, ObjectDoesNotExist):
|
||||
raise FeedDoesNotExist
|
||||
else:
|
||||
obj = None
|
||||
|
||||
current_site = sites.get_current()
|
||||
link = self.__get_dynamic_attr('link', obj)
|
||||
link = add_domain(current_site.domain, link)
|
||||
|
||||
feed = feedgenerator.DefaultRssFeed(
|
||||
title = self.__get_dynamic_attr('title', obj),
|
||||
link = link,
|
||||
description = self.__get_dynamic_attr('description', obj),
|
||||
language = LANGUAGE_CODE.decode()
|
||||
)
|
||||
|
||||
try:
|
||||
title_template = loader.get_template('rss/%s_title' % self.slug)
|
||||
except TemplateDoesNotExist:
|
||||
title_template = Template('{{ obj }}')
|
||||
try:
|
||||
description_template = loader.get_template('rss/%s_description' % self.slug)
|
||||
except TemplateDoesNotExist:
|
||||
description_template = Template('{{ obj }}')
|
||||
|
||||
for item in self.__get_dynamic_attr('items', obj):
|
||||
link = add_domain(current_site.domain, self.__get_dynamic_attr('item_link', item))
|
||||
enc = None
|
||||
enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
|
||||
if enc_url:
|
||||
enc = feedgenerator.Enclosure(
|
||||
url = enc_url.decode('utf-8'),
|
||||
length = str(self.__get_dynamic_attr('item_enclosure_length', item)).decode('utf-8'),
|
||||
mime_type = self.__get_dynamic_attr('item_enclosure_mime_type', item).decode('utf-8'),
|
||||
)
|
||||
feed.add_item(
|
||||
title = title_template.render(Context({'obj': item, 'site': current_site})).decode('utf-8'),
|
||||
link = link,
|
||||
description = description_template.render(Context({'obj': item, 'site': current_site})).decode('utf-8'),
|
||||
unique_id = link,
|
||||
enclosure = enc,
|
||||
pubdate = self.__get_dynamic_attr('item_pubdate', item),
|
||||
)
|
||||
return feed
|
||||
|
||||
# DEPRECATED
|
||||
class FeedConfiguration:
|
||||
def __init__(self, slug, title_cb, link_cb, description_cb, get_list_func_cb, get_list_kwargs,
|
||||
param_func=None, param_kwargs_cb=None, get_list_kwargs_cb=None, get_pubdate_cb=None,
|
||||
enc_url=None, enc_length=None, enc_mime_type=None):
|
||||
"""
|
||||
slug -- Normal Python string. Used to register the feed.
|
||||
|
||||
title_cb, link_cb, description_cb -- Functions that take the param
|
||||
(if applicable) and return a normal Python string.
|
||||
|
||||
get_list_func_cb -- Function that takes the param and returns a
|
||||
function to use in retrieving items.
|
||||
|
||||
get_list_kwargs -- Dictionary of kwargs to pass to the function
|
||||
returned by get_list_func_cb.
|
||||
|
||||
param_func -- Function to use in retrieving the param (if applicable).
|
||||
|
||||
param_kwargs_cb -- Function that takes the slug and returns a
|
||||
dictionary of kwargs to use in param_func.
|
||||
|
||||
get_list_kwargs_cb -- Function that takes the param and returns a
|
||||
dictionary to use in addition to get_list_kwargs (if applicable).
|
||||
|
||||
get_pubdate_cb -- Function that takes the object and returns a datetime
|
||||
to use as the publication date in the feed.
|
||||
|
||||
The three enc_* parameters are strings representing methods or
|
||||
attributes to call on a particular item to get its enclosure
|
||||
information. Each of those methods/attributes should return a normal
|
||||
Python string.
|
||||
"""
|
||||
self.slug = slug
|
||||
self.title_cb, self.link_cb = title_cb, link_cb
|
||||
self.description_cb = description_cb
|
||||
self.get_list_func_cb = get_list_func_cb
|
||||
self.get_list_kwargs = get_list_kwargs
|
||||
self.param_func, self.param_kwargs_cb = param_func, param_kwargs_cb
|
||||
self.get_list_kwargs_cb = get_list_kwargs_cb
|
||||
self.get_pubdate_cb = get_pubdate_cb
|
||||
assert (None == enc_url == enc_length == enc_mime_type) or (enc_url is not None and enc_length is not None and enc_mime_type is not None)
|
||||
self.enc_url = enc_url
|
||||
self.enc_length = enc_length
|
||||
self.enc_mime_type = enc_mime_type
|
||||
|
||||
def get_feed(self, param_slug=None):
|
||||
"""
|
||||
Returns a utils.feedgenerator.DefaultRssFeed object, fully populated,
|
||||
representing this FeedConfiguration.
|
||||
"""
|
||||
if param_slug:
|
||||
try:
|
||||
param = self.param_func(**self.param_kwargs_cb(param_slug))
|
||||
except ObjectDoesNotExist:
|
||||
raise FeedIsNotRegistered
|
||||
else:
|
||||
param = None
|
||||
current_site = sites.get_current()
|
||||
f = self._get_feed_generator_object(param)
|
||||
title_template = loader.get_template('rss/%s_title' % self.slug)
|
||||
description_template = loader.get_template('rss/%s_description' % self.slug)
|
||||
kwargs = self.get_list_kwargs.copy()
|
||||
if param and self.get_list_kwargs_cb:
|
||||
kwargs.update(self.get_list_kwargs_cb(param))
|
||||
get_list_func = self.get_list_func_cb(param)
|
||||
for obj in get_list_func(**kwargs):
|
||||
link = obj.get_absolute_url()
|
||||
if not link.startswith('http://'):
|
||||
link = u'http://%s%s' % (current_site.domain, link)
|
||||
enc = None
|
||||
if self.enc_url:
|
||||
enc_url = getattr(obj, self.enc_url)
|
||||
enc_length = getattr(obj, self.enc_length)
|
||||
enc_mime_type = getattr(obj, self.enc_mime_type)
|
||||
try:
|
||||
enc_url = enc_url()
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
enc_length = enc_length()
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
enc_mime_type = enc_mime_type()
|
||||
except TypeError:
|
||||
pass
|
||||
enc = feedgenerator.Enclosure(enc_url.decode('utf-8'),
|
||||
(enc_length and str(enc_length).decode('utf-8') or ''), enc_mime_type.decode('utf-8'))
|
||||
f.add_item(
|
||||
title = title_template.render(Context({'obj': obj, 'site': current_site})).decode('utf-8'),
|
||||
link = link,
|
||||
description = description_template.render(Context({'obj': obj, 'site': current_site})).decode('utf-8'),
|
||||
unique_id=link,
|
||||
enclosure=enc,
|
||||
pubdate = self.get_pubdate_cb and self.get_pubdate_cb(obj) or None,
|
||||
)
|
||||
return f
|
||||
|
||||
def _get_feed_generator_object(self, param):
|
||||
current_site = sites.get_current()
|
||||
link = self.link_cb(param).decode()
|
||||
if not link.startswith('http://'):
|
||||
link = u'http://%s%s' % (current_site.domain, link)
|
||||
return feedgenerator.DefaultRssFeed(
|
||||
title = self.title_cb(param).decode(),
|
||||
link = link,
|
||||
description = self.description_cb(param).decode(),
|
||||
language = LANGUAGE_CODE.decode(),
|
||||
)
|
||||
|
||||
|
||||
# global dict used by register_feed and get_registered_feed
|
||||
_registered_feeds = {}
|
||||
|
||||
# DEPRECATED
|
||||
class FeedIsNotRegistered(Exception):
|
||||
pass
|
||||
|
||||
# DEPRECATED
|
||||
def register_feed(feed):
|
||||
_registered_feeds[feed.slug] = feed
|
||||
|
||||
def register_feeds(*feeds):
|
||||
for f in feeds:
|
||||
_registered_feeds[f.slug] = f
|
||||
|
||||
def get_registered_feed(slug):
|
||||
# try to load a RSS settings module so that feeds can be registered
|
||||
try:
|
||||
__import__(SETTINGS_MODULE + '_rss', '', '', [''])
|
||||
except (KeyError, ImportError, ValueError):
|
||||
pass
|
||||
try:
|
||||
return _registered_feeds[slug]
|
||||
except KeyError:
|
||||
raise FeedIsNotRegistered
|
|
@ -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)
|
||||
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"rss", {u"version": self._version})
|
||||
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'], {})
|
||||
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)
|
||||
handler.endElement(u"rss")
|
||||
|
||||
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'])
|
||||
|
||||
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")
|
||||
# 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})
|
||||
|
||||
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
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
from django.core import rss
|
||||
from django.core.exceptions import Http404
|
||||
from django.utils.httpwrappers import HttpResponse
|
||||
|
||||
def feed(request, slug, param=None):
|
||||
try:
|
||||
f = rss.get_registered_feed(slug).get_feed(param)
|
||||
except (rss.FeedIsNotRegistered, rss.FeedDoesNotExist):
|
||||
raise Http404
|
||||
response = HttpResponse(mimetype='application/xml')
|
||||
f.write(response, 'utf-8')
|
||||
return response
|
|
@ -0,0 +1,548 @@
|
|||
==============================
|
||||
The syndication feed framework
|
||||
==============================
|
||||
|
||||
Django comes with a high-level syndication-feed-generating framework that makes
|
||||
creating RSS_ and Atom_ feeds easy.
|
||||
|
||||
To create any syndication feed, all you have to do is write a short Python
|
||||
class. You can create as many feeds as you want.
|
||||
|
||||
Django also comes with a lower-level feed-generating API. Use this if you want
|
||||
to generate feeds outside of a Web context, or in some other lower-level way.
|
||||
|
||||
.. _RSS: http://www.whatisrss.com/
|
||||
.. _Atom: http://www.atomenabled.org/
|
||||
|
||||
The high-level framework
|
||||
========================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The high-level feed-generating framework is a view that's hooked to ``/feeds/``
|
||||
by default. Django uses the remainder of the URL (everything after ``/feeds/``)
|
||||
to determine which feed to output.
|
||||
|
||||
To create a feed, just write a ``Feed`` class and point to it in your URLconf_.
|
||||
|
||||
.. _URLconf: http://www.djangoproject.com/documentation/url_dispatch/
|
||||
|
||||
Initialization
|
||||
--------------
|
||||
|
||||
To activate syndication feeds on your Django site, add this line to your
|
||||
URLconf_::
|
||||
|
||||
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
|
||||
|
||||
This tells Django to use the RSS framework to handle all URLs starting with
|
||||
``"feeds/"``. (You can change that ``"feeds/"`` prefix to fit your own needs.)
|
||||
|
||||
This URLconf line has an extra argument: ``{'feed_dict': feeds}``. Use this
|
||||
extra argument to pass the syndication framework the feeds that should be
|
||||
published under that URL.
|
||||
|
||||
Specifically, ``feed_dict`` should be a dictionary that maps a feed's slug
|
||||
(short URL label) to its ``Feed`` class.
|
||||
|
||||
You can define the ``feed_dict`` in the URLconf itself. Here's a full example
|
||||
URLconf::
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from myproject.feeds import LatestEntries, LatestEntriesByCategory
|
||||
|
||||
feeds = {
|
||||
'latest': LatestEntries,
|
||||
'categories': LatestEntriesByCategory,
|
||||
}
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# ...
|
||||
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
|
||||
{'feed_dict': feeds}),
|
||||
# ...
|
||||
)
|
||||
|
||||
The above example registers two feeds:
|
||||
|
||||
* The feed represented by ``LatestEntries`` will live at ``feeds/latest/``.
|
||||
* The feed represented by ``LatestEntriesByCategory`` will live at
|
||||
``feeds/categories/``.
|
||||
|
||||
Once that's set up, you just need to define the ``Feed`` classes themselves.
|
||||
|
||||
.. _URLconf: http://www.djangoproject.com/documentation/url_dispatch/
|
||||
.. _settings file: http://www.djangoproject.com/documentation/settings/
|
||||
|
||||
Feed classes
|
||||
------------
|
||||
|
||||
A ``Feed`` class is a simple Python class that represents a syndication feed.
|
||||
A feed can be simple (e.g., a "site news" feed, or a basic feed displaying
|
||||
the latest entries of a blog) or more complex (e.g., a feed displaying all the
|
||||
blog entries in a particular category, where the category is variable).
|
||||
|
||||
``Feed`` classes must subclass ``django.contrib.syndication.feeds.Feed``. They
|
||||
can live anywhere in your codebase.
|
||||
|
||||
A simple example
|
||||
----------------
|
||||
|
||||
This simple example, taken from chicagocrime.org, describes a feed of the
|
||||
latest five news items::
|
||||
|
||||
from django.contrib.syndication.feeds import Feed
|
||||
from django.models.chicagocrime import newsitems
|
||||
|
||||
class SiteNewsFeed(Feed):
|
||||
title = "Chicagocrime.org site news"
|
||||
link = "/sitenews/"
|
||||
description = "Updates on changes and additions to chicagocrime.org."
|
||||
|
||||
def items(self):
|
||||
return newsitems.get_list(order_by=('-pub_date',), limit=5)
|
||||
|
||||
Note:
|
||||
|
||||
* The class subclasses ``django.contrib.syndication.feeds.Feed``.
|
||||
* ``title``, ``link`` and ``description`` correspond to the standard
|
||||
RSS ``<title>``, ``<link>`` and ``<description>`` elements, respectively.
|
||||
* ``items()`` is, simply, a method that returns a list of objects that
|
||||
should be included in the feed as ``<item>`` elements. Although this
|
||||
example returns ``NewsItem`` objects using Django's
|
||||
`object-relational mapper`_, ``items()`` doesn't have to return model
|
||||
instances. Although you get a few bits of functionality "for free" by
|
||||
using Django models, ``items()`` can return any type of object you want.
|
||||
|
||||
One thing's left to do. In an RSS feed, each ``<item>`` has a ``<title>``,
|
||||
``<link>`` and ``<description>``. We need to tell the framework what data to
|
||||
put into those elements.
|
||||
|
||||
* To specify the contents of ``<title>`` and ``<description>``, create
|
||||
`Django templates`_ called ``feeds/sitenews_title`` and
|
||||
``feeds/sitenews_description``, where ``sitenews`` is the ``slug``
|
||||
specified in the URLconf for the given feed. The RSS system renders that
|
||||
template for each item, passing it two template context variables:
|
||||
* ``{{ obj }}`` -- The current object (one of whichever objects you
|
||||
returned in ``items()``).
|
||||
* ``{{ site }}`` -- A ``django.models.core.sites.Site`` object
|
||||
representing the current site. This is useful for
|
||||
``{{ site.domain }}`` or ``{{ site.name }}``.
|
||||
If you don't create a template for either the title or description, the
|
||||
framework will use the template ``{{ obj }}`` by default -- that is, the
|
||||
normal string representation of the object.
|
||||
* To specify the contents of ``<link>``, you have two options. For each
|
||||
item in ``items()``, Django first tries executing a
|
||||
``get_absolute_url()`` method on that object. If that method doesn't
|
||||
exist, it tries calling a method ``item_link()`` in the ``Feed`` class,
|
||||
passing it a single parameter, ``item``, which is the object itself.
|
||||
Both ``get_absolute_url()`` and ``item_link()`` should return the item's
|
||||
URL as a normal Python string.
|
||||
|
||||
.. _object-relational mapper: http://www.djangoproject.com/documentation/db_api/
|
||||
.. _Django templates: http://www.djangoproject.com/documentation/templates/
|
||||
|
||||
A complex example
|
||||
-----------------
|
||||
|
||||
The framework also supports more complex feeds, via parameters.
|
||||
|
||||
For example, chicagocrime.org offers an RSS feed of recent crimes for every
|
||||
police beat in Chicago. It'd be silly to create a separate ``Feed`` class for
|
||||
each police beat; that would violate the `DRY principle`_ and would couple data
|
||||
to programming logic. Instead, the RSS framework lets you make generic feeds
|
||||
that output items based on information in the feed's URL.
|
||||
|
||||
On chicagocrime.org, the police-beat feeds are accessible via URLs like this:
|
||||
|
||||
* ``/rss/beats/0613/`` -- Returns recent crimes for beat 0613.
|
||||
* ``/rss/beats/1424/`` -- Returns recent crimes for beat 1424.
|
||||
|
||||
The slug here is ``beats``. The syndication framework sees the extra URL bits
|
||||
after the slug -- ``0613`` and ``1424`` -- and gives you a hook to tell it what
|
||||
those URL bits mean, and how they should influence which items get published in
|
||||
the feed.
|
||||
|
||||
An example makes this clear. Here's the code for these beat-specific feeds::
|
||||
|
||||
class BeatFeed(Feed):
|
||||
def get_object(self, bits):
|
||||
# In case of "/rss/beats/0613/foo/bar/baz/", or other such clutter,
|
||||
# check that bits has only one member.
|
||||
if len(bits) != 1:
|
||||
raise ObjectDoesNotExist
|
||||
return beats.get_object(beat__exact=bits[0])
|
||||
|
||||
def title(self, obj):
|
||||
return "Chicagocrime.org: Crimes for beat %s" % obj.beat
|
||||
|
||||
def link(self, obj):
|
||||
return obj.get_absolute_url()
|
||||
|
||||
def description(self, obj):
|
||||
return "Crimes recently reported in police beat %s" % obj.beat
|
||||
|
||||
def items(self, obj):
|
||||
return crimes.get_list(beat__id__exact=obj.id, order_by=(('-crime_date'),), limit=30)
|
||||
|
||||
Here's the basic algorithm the RSS framework follows, given this class and a
|
||||
request to the URL ``/rss/beats/0613/``:
|
||||
|
||||
* The framework gets the URL ``/rss/beats/0613/`` and notices there's
|
||||
an extra bit of URL after the slug. It splits that remaining string by
|
||||
the slash character (``"/"``) and calls the ``Feed`` class'
|
||||
``get_object()`` method, passing it the bits. In this case, bits is
|
||||
``['0613']``. For a request to ``/rss/beats/0613/foo/bar/``, bits would
|
||||
be ``['0613', 'foo', 'bar']``.
|
||||
* ``get_object()`` is responsible for retrieving the given beat, from the
|
||||
given ``bits``. In this case, it uses the Django database API to retrieve
|
||||
the beat. Note that ``get_object()`` should raise
|
||||
``django.core.exceptions.ObjectDoesNotExist`` if given invalid
|
||||
parameters. There's no ``try``/``except`` around the
|
||||
``beats.get_object()`` call, because it's not necessary; that function
|
||||
raises ``BeatDoesNotExist`` on failure, and ``BeatDoesNotExist`` is a
|
||||
subclass of ``ObjectDoesNotExist``. Raising ``ObjectDoesNotExist`` in
|
||||
``get_object()`` tells Django to produce a 404 error for that request.
|
||||
* To generate the feed's ``<title>``, ``<link>`` and ``<description>``,
|
||||
Django uses the ``title``, ``link`` and ``description`` methods. In the
|
||||
previous example, they were simple string class attributes, but this
|
||||
example illustrates that they can be either strings *or* methods. For
|
||||
each of ``title``, ``link`` and ``description``, Django follows this
|
||||
algorithm:
|
||||
* First, it tries to call a method, passing the ``obj`` argument, where
|
||||
``obj`` is the object returned by ``get_object()``.
|
||||
* Failing that, it tries to call a method with no arguments.
|
||||
* Failing that, it uses the class attribute.
|
||||
* Finally, note that ``items()`` in this example also takes the ``obj``
|
||||
argument. The algorithm for ``items`` is the same as described in the
|
||||
previous step -- first, it tries ``items(obj)``, then ``items()``, then
|
||||
finally an ``items`` class attribute (which should be a list).
|
||||
|
||||
The ``ExampleFeed`` class below gives full documentation on methods and
|
||||
attributes of ``Feed`` classes.
|
||||
|
||||
.. _DRY principle: http://c2.com/cgi/wiki?DontRepeatYourself
|
||||
|
||||
Specifying the type of feed
|
||||
---------------------------
|
||||
|
||||
By default, feeds produced in this framework use RSS 2.0.
|
||||
|
||||
To change that, add a ``feed_type`` attribute to your ``Feed`` class, like so::
|
||||
|
||||
from django.utils.feedgenerator import Atom1Feed
|
||||
|
||||
class MyFeed(Feed):
|
||||
feed_type = Atom1Feed
|
||||
|
||||
Note that you set ``feed_type`` to a class object, not an instance.
|
||||
|
||||
Currently available feed types are::
|
||||
|
||||
* ``django.utils.feedgenerator.Rss201rev2Feed`` (RSS 2.01. Default.)
|
||||
* ``django.utils.feedgenerator.RssUserland091Feed`` (RSS 0.91.)
|
||||
* ``django.utils.feedgenerator.Atom1Feed`` (Atom 1.0.)
|
||||
|
||||
Enclosures
|
||||
----------
|
||||
|
||||
To specify enclosures, such as those used in creating podcast feeds, use the
|
||||
``item_enclosure_url``, ``item_enclosure_length`` and
|
||||
``item_enclosure_mime_type`` hooks. See the ``ExampleFeed`` class below for
|
||||
usage examples.
|
||||
|
||||
Language
|
||||
--------
|
||||
|
||||
Feeds created by the syndication framework automatically include the
|
||||
appropriate ``<language>`` tag (RSS 2.0) or ``xml:lang`` attribute (Atom). This
|
||||
comes directly from your `LANGUAGE_CODE setting`_.
|
||||
|
||||
.. _LANGUAGE_CODE setting: http://www.djangoproject.com/documentation/settings/#language-code
|
||||
|
||||
Publishing Atom and RSS feeds in tandem
|
||||
---------------------------------------
|
||||
|
||||
Some developers like to make available both Atom *and* RSS versions of their
|
||||
feeds. That's easy to do with Django: Just create a subclass of your ``feed``
|
||||
class and set the ``feed_type`` to something different. Then update your
|
||||
URLconf to add the extra versions.
|
||||
|
||||
Here's a full example::
|
||||
|
||||
from django.contrib.syndication.feeds import Feed
|
||||
from django.models.chicagocrime import newsitems
|
||||
from django.utils.feedgenerator import Atom1Feed
|
||||
|
||||
class RssSiteNewsFeed(Feed):
|
||||
title = "Chicagocrime.org site news"
|
||||
link = "/sitenews/"
|
||||
description = "Updates on changes and additions to chicagocrime.org."
|
||||
|
||||
def items(self):
|
||||
return newsitems.get_list(order_by=('-pub_date',), limit=5)
|
||||
|
||||
class AtomSiteNewsFeed(RssSiteNewsFeed):
|
||||
feed_type = Atom1Feed
|
||||
|
||||
And the accompanying URLconf::
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from myproject.feeds import RssSiteNewsFeed, AtomSiteNewsFeed
|
||||
|
||||
feeds = {
|
||||
'rss': RssSiteNewsFeed,
|
||||
'atom': AtomSiteNewsFeed,
|
||||
}
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# ...
|
||||
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
|
||||
{'feed_dict': feeds}),
|
||||
# ...
|
||||
)
|
||||
|
||||
Feed class reference
|
||||
-------------------
|
||||
|
||||
This example illustrates all possible attributes and methods for a ``Feed`` class::
|
||||
|
||||
class ExampleFeed(rss.Feed):
|
||||
|
||||
# FEED TYPE -- Optional. This should be a class that subclasses
|
||||
# django.utils.feedgenerator.SyndicationFeed. This designates which
|
||||
# type of feed this should be: RSS 2.0, Atom 1.0, etc.
|
||||
# If you don't specify feed_type, your feed will be RSS 2.0.
|
||||
# This should be a class, not an instance of the class.
|
||||
|
||||
feed_type = feedgenerator.Rss201rev2Feed
|
||||
|
||||
# TITLE -- One of the following three is required. The framework looks
|
||||
# for them in this order.
|
||||
|
||||
def title(self, obj):
|
||||
"""
|
||||
Takes the object returned by get_object() and returns the feed's
|
||||
title as a normal Python string.
|
||||
"""
|
||||
|
||||
def title(self):
|
||||
"""
|
||||
Returns the feed's title as a normal Python string.
|
||||
"""
|
||||
|
||||
title = 'foo' # Hard-coded title.
|
||||
|
||||
# LINK -- One of the following three is required. The framework looks
|
||||
# for them in this order.
|
||||
|
||||
def link(self, obj):
|
||||
"""
|
||||
Takes the object returned by get_object() and returns the feed's
|
||||
link as a normal Python string.
|
||||
"""
|
||||
|
||||
def link(self):
|
||||
"""
|
||||
Returns the feed's link as a normal Python string.
|
||||
"""
|
||||
|
||||
link = '/foo/bar/' # Hard-coded link.
|
||||
|
||||
# DESCRIPTION -- One of the following three is required. The framework
|
||||
# looks for them in this order.
|
||||
|
||||
def description(self, obj):
|
||||
"""
|
||||
Takes the object returned by get_object() and returns the feed's
|
||||
description as a normal Python string.
|
||||
"""
|
||||
|
||||
def description(self):
|
||||
"""
|
||||
Returns the feed's description as a normal Python string.
|
||||
"""
|
||||
|
||||
description = 'Foo bar baz.' # Hard-coded description.
|
||||
|
||||
# ITEMS -- One of the following three is required. The framework looks
|
||||
# for them in this order.
|
||||
|
||||
def items(self, obj):
|
||||
"""
|
||||
Takes the object returned by get_object() and returns a list of
|
||||
items to publish in this feed.
|
||||
"""
|
||||
|
||||
def items(self):
|
||||
"""
|
||||
Returns a list of items to publish in this feed.
|
||||
"""
|
||||
|
||||
items = ('Item 1', 'Item 2') # Hard-coded items.
|
||||
|
||||
# GET_OBJECT -- This is required for feeds that publish different data
|
||||
# for different URL parameters. (See "A complex example" above.)
|
||||
|
||||
def get_object(self, bits):
|
||||
"""
|
||||
Takes a list of strings gleaned from the URL and returns an object
|
||||
represented by this feed. Raises
|
||||
django.core.exceptions.ObjectDoesNotExist on error.
|
||||
"""
|
||||
|
||||
# ITEM LINK -- One of these three is required. The framework looks for
|
||||
# them in this order.
|
||||
|
||||
# First, the framework tries the get_absolute_url() method on each item
|
||||
# returned by items(). Failing that, it tries these two methods:
|
||||
|
||||
def item_link(self, item):
|
||||
"""
|
||||
Takes an item, as returned by items(), and returns the item's URL.
|
||||
"""
|
||||
|
||||
def item_link(self):
|
||||
"""
|
||||
Returns the URL for every item in the feed.
|
||||
"""
|
||||
|
||||
# ITEM ENCLOSURE URL -- One of these three is required if you're
|
||||
# publishing enclosures. The framework looks for them in this order.
|
||||
|
||||
def item_enclosure_url(self, item):
|
||||
"""
|
||||
Takes an item, as returned by items(), and returns the item's
|
||||
enclosure URL.
|
||||
"""
|
||||
|
||||
def item_enclosure_url(self):
|
||||
"""
|
||||
Returns the enclosure URL for every item in the feed.
|
||||
"""
|
||||
|
||||
item_enclosure_url = "/foo/bar.mp3" # Hard-coded enclosure link.
|
||||
|
||||
# ITEM ENCLOSURE LENGTH -- One of these three is required if you're
|
||||
# publishing enclosures. The framework looks for them in this order.
|
||||
# In each case, the returned value should be either an integer, or a
|
||||
# string representation of the integer, in bytes.
|
||||
|
||||
def item_enclosure_length(self, item):
|
||||
"""
|
||||
Takes an item, as returned by items(), and returns the item's
|
||||
enclosure length.
|
||||
"""
|
||||
|
||||
def item_enclosure_length(self):
|
||||
"""
|
||||
Returns the enclosure length for every item in the feed.
|
||||
"""
|
||||
|
||||
item_enclosure_length = 32000 # Hard-coded enclosure length.
|
||||
|
||||
# ITEM ENCLOSURE MIME TYPE -- One of these three is required if you're
|
||||
# publishing enclosures. The framework looks for them in this order.
|
||||
|
||||
def item_enclosure_mime_type(self, item):
|
||||
"""
|
||||
Takes an item, as returned by items(), and returns the item's
|
||||
enclosure mime type.
|
||||
"""
|
||||
|
||||
def item_enclosure_mime_type(self):
|
||||
"""
|
||||
Returns the enclosure length, in bytes, for every item in the feed.
|
||||
"""
|
||||
|
||||
item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure mime-type.
|
||||
|
||||
# ITEM PUBDATE -- It's optional to use one of these three. This is a
|
||||
# hook that specifies how to get the pubdate for a given item.
|
||||
# In each case, the method/attribute should return a Python
|
||||
# datetime.datetime object.
|
||||
|
||||
def item_pubdate(self, item):
|
||||
"""
|
||||
Takes an item, as returned by items(), and returns the item's
|
||||
pubdate.
|
||||
"""
|
||||
|
||||
def item_pubdate(self):
|
||||
"""
|
||||
Returns the pubdate for every item in the feed.
|
||||
"""
|
||||
|
||||
item_pubdate = datetime.datetime(2005, 5, 3) # Hard-coded pubdate.
|
||||
|
||||
The low-level framework
|
||||
=======================
|
||||
|
||||
Behind the scenes, the high-level RSS framework uses a lower-level framework
|
||||
for generating feeds' XML. This framework lives in a single module:
|
||||
`django/utils/feedgenerator.py`_.
|
||||
|
||||
Feel free to use this framework on your own, for lower-level tasks.
|
||||
|
||||
The ``feedgenerator`` module contains a base class ``SyndicationFeed`` and
|
||||
several subclasses:
|
||||
|
||||
* ``RssUserland091Feed``
|
||||
* ``Rss201rev2Feed``
|
||||
* ``Atom1Feed``
|
||||
|
||||
Each of these three classes knows how to render a certain type of feed as XML.
|
||||
They share this interface::
|
||||
|
||||
``__init__(title, link, description, language=None, author_email=None,
|
||||
author_name=None, author_link=None, subtitle=None, categories=None)``
|
||||
|
||||
Initializes the feed with the given metadata, which applies to the entire feed
|
||||
(i.e., not just to a specific item in the feed).
|
||||
|
||||
All parameters, if given, should be Unicode objects, except ``categories``,
|
||||
which should be a sequence of Unicode objects.
|
||||
|
||||
``add_item(title, link, description, author_email=None, author_name=None,
|
||||
pubdate=None, comments=None, unique_id=None, enclosure=None, categories=())``
|
||||
|
||||
Add an item to the feed with the given parameters. All parameters, if given,
|
||||
should be Unicode objects, except:
|
||||
|
||||
* ``pubdate`` should be a Python datetime object.
|
||||
* ``enclosure`` should be an instance of ``feedgenerator.Enclosure``.
|
||||
* ``categories`` should be a sequence of Unicode objects.
|
||||
|
||||
``write(outfile, encoding)``
|
||||
|
||||
Outputs the feed in the given encoding to outfile, which is a file-like object.
|
||||
|
||||
``writeString(encoding)``
|
||||
|
||||
Returns the feed as a string in the given encoding.
|
||||
|
||||
Example usage
|
||||
-------------
|
||||
|
||||
This example creates an Atom 1.0 feed and prints it to standard output::
|
||||
|
||||
>>> from django.utils import feedgenerator
|
||||
>>> f = feedgenerator.Atom1Feed(
|
||||
... title=u"My Weblog",
|
||||
... link=u"http://www.example.com/",
|
||||
... description=u"In which I write about what I ate today.",
|
||||
... language=u"en"),
|
||||
>>> f.add_item(title=u"Hot dog today",
|
||||
... link=u"http://www.example.com/entries/1/",
|
||||
... description=u"<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>")
|
||||
>>> print f.writeString('utf8')
|
||||
<?xml version="1.0" encoding="utf8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title>My Weblog</title>
|
||||
<link href="http://www.example.com/"></link><id>http://www.example.com/</id>
|
||||
<updated>Sat, 12 Nov 2005 00:28:43 -0000</updated><entry><title>Hot dog today</title>
|
||||
<link>http://www.example.com/entries/1/</link><id>tag:www.example.com/entries/1/</id>
|
||||
<summary type="html"><p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p></summary>
|
||||
</entry></feed>
|
||||
|
||||
.. _django/utils/feedgenerator.py: http://code.djangoproject.com/browser/django/trunk/django/utils/feedgenerator.py
|
Loading…
Reference in New Issue