You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
991 lines
36 KiB
991 lines
36 KiB
.. _ref-contrib-syndication: |
|
|
|
============================== |
|
The syndication feed framework |
|
============================== |
|
|
|
.. module:: django.contrib.syndication |
|
:synopsis: A framework for generating syndication feeds, in RSS and Atom, |
|
quite easily. |
|
|
|
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 :class:`~django.contrib.syndication.feeds.Feed` |
|
class and point to it in your :ref:`URLconf <topics-http-urls>`. |
|
|
|
Initialization |
|
-------------- |
|
|
|
If you're not using the latest Django development version, you'll need to make |
|
sure Django's sites framework is installed -- including its database table. (See |
|
the :mod:`sites framework documentation <django.contrib.sites>` for more |
|
information.) This has changed in the Django development version; the |
|
syndication feed framework no longer requires the sites framework. |
|
|
|
To activate syndication feeds on your Django site, add this line to your |
|
:ref:`URLconf <topics-http-urls>`:: |
|
|
|
(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 |
|
:file:`"feeds/"`. (You can change that :file:`"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, :data:`feed_dict` should be a dictionary that maps a feed's slug |
|
(short URL label) to its :class:`~django.contrib.syndication.feeds.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 |
|
:class:`~django.contrib.syndication.feeds.Feed` classes themselves. |
|
|
|
Feed classes |
|
------------ |
|
|
|
A :class:`~django.contrib.syndication.feeds.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). |
|
|
|
:class:`~django.contrib.syndication.feeds.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 chicagocrime.models import NewsItem |
|
|
|
class LatestEntries(Feed): |
|
title = "Chicagocrime.org site news" |
|
link = "/sitenews/" |
|
description = "Updates on changes and additions to chicagocrime.org." |
|
|
|
def items(self): |
|
return NewsItem.objects.order_by('-pub_date')[:5] |
|
|
|
Note: |
|
|
|
* The class subclasses ``django.contrib.syndication.feeds.Feed``. |
|
|
|
* :attr:`title`, :attr:`link` and :attr:`description` correspond to the |
|
standard RSS ``<title>``, ``<link>`` and ``<description>`` elements, |
|
respectively. |
|
|
|
* :meth:`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 |
|
:ref:`object-relational mapper <ref-models-querysets>`, :meth:`items()` |
|
doesn't have to return model instances. Although you get a few bits of |
|
functionality "for free" by using Django models, :meth:`items()` can |
|
return any type of object you want. |
|
|
|
* If you're creating an Atom feed, rather than an RSS feed, set the |
|
:attr:`subtitle` attribute instead of the :attr:`description` attribute. |
|
See `Publishing Atom and RSS feeds in tandem`_, later, for an example. |
|
|
|
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 |
|
:ref:`Django templates <topics-templates>` called |
|
:file:`feeds/latest_title.html`` and |
|
:file:`feeds/latest_description.html`, where :attr:`latest` is the |
|
:attr:`slug` specified in the URLconf for the given feed. Note the |
|
``.html`` extension is required. 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 :meth:`items()`). |
|
|
|
* ``{{ site }}`` -- A :class:`django.contrib.sites.models.Site` object |
|
representing the current site. This is useful for ``{{ site.domain |
|
}}`` or ``{{ site.name }}``. Note that if you're using the latest |
|
Django development version and do *not* have the Django sites |
|
framework installed, this will be set to a |
|
:class:`django.contrib.sites.models.RequestSite` object. See the |
|
:ref:`RequestSite section of the sites framework documentation |
|
<requestsite-objects>` for more. |
|
|
|
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. You can also change the names |
|
of these two templates by specifying ``title_template`` and |
|
``description_template`` as attributes of your |
|
:class:`~django.contrib.syndication.feeds.Feed` class. |
|
|
|
* To specify the contents of ``<link>``, you have two options. For each item |
|
in :meth:`items()`, Django first tries executing a ``get_absolute_url()`` |
|
method on that object. If that method doesn't exist, it tries calling a |
|
method :meth:`item_link()` in the |
|
:class:`~django.contrib.syndication.feeds.Feed` class, passing it a single |
|
parameter, :attr:`item`, which is the object itself. Both |
|
``get_absolute_url()`` and :meth:`item_link()` should return the item's |
|
URL as a normal Python string. As with ``get_absolute_url()``, the result |
|
of :meth:`item_link()` will be included directly in the URL, so you are |
|
responsible for doing all necessary URL quoting and conversion to ASCII |
|
inside the method itself. |
|
|
|
* For the LatestEntries example above, we could have very simple feed |
|
templates: |
|
|
|
* latest_title.html: |
|
|
|
.. code-block:: html+django |
|
|
|
{{ obj.title }} |
|
|
|
* latest_description.html: |
|
|
|
.. code-block:: html+django |
|
|
|
{{ obj.description }} |
|
|
|
.. _chicagocrime.org: http://www.chicagocrime.org/ |
|
|
|
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 |
|
:class:`~django.contrib.syndication.feeds.Feed` class for each police beat; that |
|
would violate the :ref:`DRY principle <dry>` and would couple data to |
|
programming logic. Instead, the syndication 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: |
|
|
|
* :file:`/rss/beats/0613/` -- Returns recent crimes for beat 0613. |
|
* :file:`/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:: |
|
|
|
from django.contrib.syndication.feeds import FeedDoesNotExist |
|
|
|
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 Beat.objects.get(beat__exact=bits[0]) |
|
|
|
def title(self, obj): |
|
return "Chicagocrime.org: Crimes for beat %s" % obj.beat |
|
|
|
def link(self, obj): |
|
if not obj: |
|
raise FeedDoesNotExist |
|
return obj.get_absolute_url() |
|
|
|
def description(self, obj): |
|
return "Crimes recently reported in police beat %s" % obj.beat |
|
|
|
def items(self, obj): |
|
return Crime.objects.filter(beat__id__exact=obj.id).order_by('-crime_date')[:30] |
|
|
|
Here's the basic algorithm the RSS framework follows, given this class and a |
|
request to the URL :file:`/rss/beats/0613/`: |
|
|
|
* The framework gets the URL :file:`/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 |
|
:class:`~django.contrib.syndication.feeds.Feed` class' |
|
:meth:`get_object()` method, passing it the bits. In this case, bits is |
|
``['0613']``. For a request to :file:`/rss/beats/0613/foo/bar/`, bits |
|
would be ``['0613', 'foo', 'bar']``. |
|
|
|
* :meth:`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 :meth:`get_object()` should raise |
|
:exc:`django.core.exceptions.ObjectDoesNotExist` if given invalid |
|
parameters. There's no ``try``/``except`` around the |
|
``Beat.objects.get()`` call, because it's not necessary; that function |
|
raises :exc:`Beat.DoesNotExist` on failure, and :exc:`Beat.DoesNotExist` |
|
is a subclass of :exc:`ObjectDoesNotExist`. Raising |
|
:exc:`ObjectDoesNotExist` in :meth:`get_object()` tells Django to produce |
|
a 404 error for that request. |
|
|
|
.. versionadded:: 1.0 |
|
meth:`get_object()` can handle the :file:`/rss/beats/` url. |
|
|
|
The :meth:`get_object()` method also has a chance to handle the |
|
:file:`/rss/beats/` url. In this case, :data:`bits` will be an |
|
empty list. In our example, ``len(bits) != 1`` and an |
|
:exc:`ObjectDoesNotExist` exception will be raised, so |
|
:file:`/rss/beats/` will generate a 404 page. But you can handle this case |
|
however you like. For example, you could generate a combined feed for all |
|
beats. |
|
|
|
* To generate the feed's ``<title>``, ``<link>`` and ``<description>``, |
|
Django uses the :meth:`title()`, :meth:`link()` and :meth:`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 :attr:`title`, :attr:`link` and |
|
:attr:`description`, Django follows this algorithm: |
|
|
|
* First, it tries to call a method, passing the ``obj`` argument, where |
|
``obj`` is the object returned by :meth:`get_object()`. |
|
|
|
* Failing that, it tries to call a method with no arguments. |
|
|
|
* Failing that, it uses the class attribute. |
|
|
|
Inside the :meth:`link()` method, we handle the possibility that ``obj`` |
|
might be ``None``, which can occur when the URL isn't fully specified. In |
|
some cases, you might want to do something else in this case, which would |
|
mean you'd need to check for ``obj`` existing in other methods as well. |
|
(The :meth:`link()` method is called very early in the feed generation |
|
process, so it's a good place to bail out early.) |
|
|
|
* Finally, note that :meth:`items()` in this example also takes the ``obj`` |
|
argument. The algorithm for :attr:`items` is the same as described in the |
|
previous step -- first, it tries :meth:`items(obj)`, then :meth:`items()`, |
|
then finally an :attr:`items` class attribute (which should be a list). |
|
|
|
The ``ExampleFeed`` class below gives full documentation on methods and |
|
attributes of :class:`~django.contrib.syndication.feeds.Feed` classes. |
|
|
|
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 |
|
:class:`~django.contrib.syndication.feeds.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: |
|
|
|
* :class:`django.utils.feedgenerator.Rss201rev2Feed` (RSS 2.01. Default.) |
|
* :class:`django.utils.feedgenerator.RssUserland091Feed` (RSS 0.91.) |
|
* :class:`django.utils.feedgenerator.Atom1Feed` (Atom 1.0.) |
|
|
|
Enclosures |
|
---------- |
|
|
|
To specify enclosures, such as those used in creating podcast feeds, use the |
|
:attr:`item_enclosure_url`, :attr:`item_enclosure_length` and |
|
:attr:`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 :setting:`LANGUAGE_CODE setting`. |
|
|
|
URLs |
|
---- |
|
|
|
The :attr:`link` method/attribute can return either an absolute URL (e.g. |
|
:file:`"/blog/"`) or a URL with the fully-qualified domain and protocol (e.g. |
|
``"http://www.example.com/blog/"``). If :attr:`link` doesn't return the domain, |
|
the syndication framework will insert the domain of the current site, according |
|
to your :setting:`SITE_ID setting <SITE_ID>`. |
|
|
|
Atom feeds require a ``<link rel="self">`` that defines the feed's current |
|
location. The syndication framework populates this automatically, using the |
|
domain of the current site according to the :setting:`SITE_ID` setting. |
|
|
|
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 |
|
:class:`~django.contrib.syndication.feeds.Feed` |
|
class and set the :attr:`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 chicagocrime.models import NewsItem |
|
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 NewsItem.objects.order_by('-pub_date')[:5] |
|
|
|
class AtomSiteNewsFeed(RssSiteNewsFeed): |
|
feed_type = Atom1Feed |
|
subtitle = RssSiteNewsFeed.description |
|
|
|
.. Note:: |
|
In this example, the RSS feed uses a :attr:`description` while the Atom |
|
feed uses a :attr:`subtitle`. That's because Atom feeds don't provide for |
|
a feed-level "description," but they *do* provide for a "subtitle." |
|
|
|
If you provide a :attr:`description` in your |
|
:class:`~django.contrib.syndication.feeds.Feed` class, Django will *not* |
|
automatically put that into the :attr:`subtitle` element, because a |
|
subtitle and description are not necessarily the same thing. Instead, you |
|
should define a :attr:`subtitle` attribute. |
|
|
|
In the above example, we simply set the Atom feed's :attr:`subtitle` to the |
|
RSS feed's :attr:`description`, because it's quite short already. |
|
|
|
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 |
|
-------------------- |
|
|
|
.. class:: django.contrib.syndication.feeds.Feed |
|
|
|
This example illustrates all possible attributes and methods for a |
|
:class:`~django.contrib.syndication.feeds.Feed` class:: |
|
|
|
from django.contrib.syndication.feeds import Feed |
|
from django.utils import feedgenerator |
|
|
|
class ExampleFeed(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 |
|
|
|
# TEMPLATE NAMES -- Optional. These should be strings representing |
|
# names of Django templates that the system should use in rendering the |
|
# title and description of your feed items. Both are optional. |
|
# If you don't specify one, or either, Django will use the template |
|
# 'feeds/SLUG_title.html' and 'feeds/SLUG_description.html', where SLUG |
|
# is the slug you specify in the URL. |
|
|
|
title_template = None |
|
description_template = None |
|
|
|
# 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. |
|
|
|
# GUID -- One of the following three is optional. The framework looks |
|
# for them in this order. This property is only used for Atom feeds |
|
# (where it is the feed-level ID element). If not provided, the feed |
|
# link is used as the ID. |
|
|
|
def feed_guid(self, obj): |
|
""" |
|
Takes the object returned by get_object() and returns the globally |
|
unique ID for the feed as a normal Python string. |
|
""" |
|
|
|
def feed_guid(self): |
|
""" |
|
Returns the feed's globally unique ID as a normal Python string. |
|
""" |
|
|
|
feed_guid = '/foo/bar/1234' # Hard-coded guid. |
|
|
|
# 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. |
|
|
|
# AUTHOR NAME --One of the following three is optional. The framework |
|
# looks for them in this order. |
|
|
|
def author_name(self, obj): |
|
""" |
|
Takes the object returned by get_object() and returns the feed's |
|
author's name as a normal Python string. |
|
""" |
|
|
|
def author_name(self): |
|
""" |
|
Returns the feed's author's name as a normal Python string. |
|
""" |
|
|
|
author_name = 'Sally Smith' # Hard-coded author name. |
|
|
|
# AUTHOR E-MAIL --One of the following three is optional. The framework |
|
# looks for them in this order. |
|
|
|
def author_email(self, obj): |
|
""" |
|
Takes the object returned by get_object() and returns the feed's |
|
author's e-mail as a normal Python string. |
|
""" |
|
|
|
def author_email(self): |
|
""" |
|
Returns the feed's author's e-mail as a normal Python string. |
|
""" |
|
|
|
author_email = 'test@example.com' # Hard-coded author e-mail. |
|
|
|
# AUTHOR LINK --One of the following three is optional. The framework |
|
# looks for them in this order. In each case, the URL should include |
|
# the "http://" and domain name. |
|
|
|
def author_link(self, obj): |
|
""" |
|
Takes the object returned by get_object() and returns the feed's |
|
author's URL as a normal Python string. |
|
""" |
|
|
|
def author_link(self): |
|
""" |
|
Returns the feed's author's URL as a normal Python string. |
|
""" |
|
|
|
author_link = 'http://www.example.com/' # Hard-coded author URL. |
|
|
|
# CATEGORIES -- One of the following three is optional. The framework |
|
# looks for them in this order. In each case, the method/attribute |
|
# should return an iterable object that returns strings. |
|
|
|
def categories(self, obj): |
|
""" |
|
Takes the object returned by get_object() and returns the feed's |
|
categories as iterable over strings. |
|
""" |
|
|
|
def categories(self): |
|
""" |
|
Returns the feed's categories as iterable over strings. |
|
""" |
|
|
|
categories = ("python", "django") # Hard-coded list of categories. |
|
|
|
# COPYRIGHT NOTICE -- One of the following three is optional. The |
|
# framework looks for them in this order. |
|
|
|
def copyright(self, obj): |
|
""" |
|
Takes the object returned by get_object() and returns the feed's |
|
copyright notice as a normal Python string. |
|
""" |
|
|
|
def copyright(self): |
|
""" |
|
Returns the feed's copyright notice as a normal Python string. |
|
""" |
|
|
|
copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice. |
|
|
|
# TTL -- One of the following three is optional. The framework looks |
|
# for them in this order. Ignored for Atom feeds. |
|
|
|
def ttl(self, obj): |
|
""" |
|
Takes the object returned by get_object() and returns the feed's |
|
TTL (Time To Live) as a normal Python string. |
|
""" |
|
|
|
def ttl(self): |
|
""" |
|
Returns the feed's TTL as a normal Python string. |
|
""" |
|
|
|
ttl = 600 # Hard-coded Time To Live. |
|
|
|
# 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 two methods below, in |
|
# order. Failing that, it falls back to the get_absolute_url() |
|
# method on each item returned by items(). |
|
|
|
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_GUID -- The following method is optional. This property is |
|
# only used for Atom feeds (it is the ID element for an item in an |
|
# Atom feed). If not provided, the item's link is used by default. |
|
|
|
def item_guid(self, obj): |
|
""" |
|
Takes an item, as return by items(), and returns the item's ID. |
|
""" |
|
|
|
# ITEM AUTHOR NAME -- One of the following three is optional. The |
|
# framework looks for them in this order. |
|
|
|
def item_author_name(self, item): |
|
""" |
|
Takes an item, as returned by items(), and returns the item's |
|
author's name as a normal Python string. |
|
""" |
|
|
|
def item_author_name(self): |
|
""" |
|
Returns the author name for every item in the feed. |
|
""" |
|
|
|
item_author_name = 'Sally Smith' # Hard-coded author name. |
|
|
|
# ITEM AUTHOR E-MAIL --One of the following three is optional. The |
|
# framework looks for them in this order. |
|
# |
|
# If you specify this, you must specify item_author_name. |
|
|
|
def item_author_email(self, obj): |
|
""" |
|
Takes an item, as returned by items(), and returns the item's |
|
author's e-mail as a normal Python string. |
|
""" |
|
|
|
def item_author_email(self): |
|
""" |
|
Returns the author e-mail for every item in the feed. |
|
""" |
|
|
|
item_author_email = 'test@example.com' # Hard-coded author e-mail. |
|
|
|
# ITEM AUTHOR LINK --One of the following three is optional. The |
|
# framework looks for them in this order. In each case, the URL should |
|
# include the "http://" and domain name. |
|
# |
|
# If you specify this, you must specify item_author_name. |
|
|
|
def item_author_link(self, obj): |
|
""" |
|
Takes an item, as returned by items(), and returns the item's |
|
author's URL as a normal Python string. |
|
""" |
|
|
|
def item_author_link(self): |
|
""" |
|
Returns the author URL for every item in the feed. |
|
""" |
|
|
|
item_author_link = 'http://www.example.com/' # Hard-coded author URL. |
|
|
|
# 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 MIME type 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. |
|
|
|
# ITEM CATEGORIES -- It's optional to use one of these three. This is |
|
# a hook that specifies how to get the list of categories for a given |
|
# item. In each case, the method/attribute should return an iterable |
|
# object that returns strings. |
|
|
|
def item_categories(self, item): |
|
""" |
|
Takes an item, as returned by items(), and returns the item's |
|
categories. |
|
""" |
|
|
|
def item_categories(self): |
|
""" |
|
Returns the categories for every item in the feed. |
|
""" |
|
|
|
item_categories = ("python", "django") # Hard-coded categories. |
|
|
|
# ITEM COPYRIGHT NOTICE (only applicable to Atom feeds) -- One of the |
|
# following three is optional. The framework looks for them in this |
|
# order. |
|
|
|
def item_copyright(self, obj): |
|
""" |
|
Takes an item, as returned by items(), and returns the item's |
|
copyright notice as a normal Python string. |
|
""" |
|
|
|
def item_copyright(self): |
|
""" |
|
Returns the copyright notice for every item in the feed. |
|
""" |
|
|
|
item_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice. |
|
|
|
|
|
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`_. |
|
|
|
You use this framework on your own, for lower-level feed generation. You can |
|
also create custom feed generator subclasses for use with the ``feed_type`` |
|
``Feed`` option. |
|
|
|
``SyndicationFeed`` classes |
|
--------------------------- |
|
|
|
The :mod:`~django.utils.feedgenerator` module contains a base class: |
|
|
|
.. class:: django.utils.feedgenerator.SyndicationFeed |
|
|
|
and several subclasses: |
|
|
|
.. class:: django.utils.feedgenerator.RssUserland091Feed |
|
.. class:: django.utils.feedgenerator.Rss201rev2Feed |
|
.. class:: django.utils.feedgenerator.Atom1Feed |
|
|
|
Each of these three classes knows how to render a certain type of feed as XML. |
|
They share this interface: |
|
|
|
.. method:: SyndicationFeed.__init__(**kwargs) |
|
|
|
Initialize the feed with the given dictionary of metadata, which applies to |
|
the entire feed. Required keyword arguments are: |
|
|
|
* ``title`` |
|
* ``link`` |
|
* ``description`` |
|
|
|
There's also a bunch of other optional keywords: |
|
|
|
* ``language`` |
|
* ``author_email`` |
|
* ``author_name`` |
|
* ``author_link`` |
|
* ``subtitle`` |
|
* ``categories`` |
|
* ``feed_url`` |
|
* ``feed_copyright`` |
|
* ``feed_guid`` |
|
* ``ttl`` |
|
|
|
Any extra keyword arguments you pass to ``__init__`` will be stored in |
|
``self.feed`` for use with `custom feed generators`_. |
|
|
|
All parameters should be Unicode objects, except ``categories``, which |
|
should be a sequence of Unicode objects. |
|
|
|
.. method:: SyndicationFeed.add_item(**kwargs) |
|
|
|
Add an item to the feed with the given parameters. |
|
|
|
Required keyword arguments are: |
|
|
|
* ``title`` |
|
* ``link`` |
|
* ``description`` |
|
|
|
Optional keyword arguments are: |
|
|
|
* ``author_email`` |
|
* ``author_name`` |
|
* ``author_link`` |
|
* ``pubdate`` |
|
* ``comments`` |
|
* ``unique_id`` |
|
* ``enclosure`` |
|
* ``categories`` |
|
* ``item_copyright`` |
|
* ``ttl`` |
|
|
|
Extra keyword arguments will be stored for `custom feed generators`_. |
|
|
|
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. |
|
|
|
.. method:: SyndicationFeed.write(outfile, encoding) |
|
|
|
Outputs the feed in the given encoding to outfile, which is a file-like object. |
|
|
|
.. method:: SyndicationFeed.writeString(encoding) |
|
|
|
Returns the feed as a string in the given encoding. |
|
|
|
For example, to create an Atom 1.0 feed and print 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"> |
|
... |
|
</feed> |
|
|
|
.. _django/utils/feedgenerator.py: http://code.djangoproject.com/browser/django/trunk/django/utils/feedgenerator.py |
|
.. _Python datetime object: http://www.python.org/doc/current/lib/module-datetime.html |
|
|
|
Custom feed generators |
|
---------------------- |
|
|
|
If you need to produce a custom feed format, you've got a couple of options. |
|
|
|
If the feed format is totally custom, you'll want to subclass |
|
``SyndicationFeed`` and completely replace the ``write()`` and |
|
``writeString()`` methods. |
|
|
|
However, if the feed format is a spin-off of RSS or Atom (i.e. GeoRSS_, Apple's |
|
`iTunes podcast format`_, etc.), you've got a better choice. These types of |
|
feeds typically add extra elements and/or attributes to the underlying format, |
|
and there are a set of methods that ``SyndicationFeed`` calls to get these extra |
|
attributes. Thus, you can subclass the appropriate feed generator class |
|
(``Atom1Feed`` or ``Rss201rev2Feed``) and extend these callbacks. They are: |
|
|
|
.. _georss: http://georss.org/ |
|
.. _itunes podcast format: http://www.apple.com/itunes/store/podcaststechspecs.html |
|
|
|
``SyndicationFeed.root_attributes(self, )`` |
|
Return a ``dict`` of attributes to add to the root feed element |
|
(``feed``/``channel``). |
|
|
|
``SyndicationFeed.add_root_elements(self, handler)`` |
|
Callback to add elements inside the root feed element |
|
(``feed``/``channel``). ``handler`` is an `XMLGenerator`_ from Python's |
|
built-in SAX library; you'll call methods on it to add to the XML |
|
document in process. |
|
|
|
``SyndicationFeed.item_attributes(self, item)`` |
|
Return a ``dict`` of attributes to add to each item (``item``/``entry``) |
|
element. The argument, ``item``, is a dictionary of all the data passed to |
|
``SyndicationFeed.add_item()``. |
|
|
|
``SyndicationFeed.add_item_elements(self, handler, item)`` |
|
Callback to add elements to each item (``item``/``entry``) element. |
|
``handler`` and ``item`` are as above. |
|
|
|
.. warning:: |
|
|
|
If you override any of these methods, be sure to call the superclass methods |
|
since they add the required elements for each feed format. |
|
|
|
For example, you might start implementing an iTunes RSS feed generator like so:: |
|
|
|
class iTunesFeed(Rss201rev2Feed): |
|
def root_attibutes(self): |
|
attrs = super(iTunesFeed, self).root_attibutes() |
|
attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd' |
|
return attrs |
|
|
|
def add_root_elements(self, handler): |
|
super(iTunesFeed, self).add_root_elements(handler) |
|
handler.addQuickElement('itunes:explicit', 'clean') |
|
|
|
Obviously there's a lot more work to be done for a complete custom feed class, |
|
but the above example should demonstrate the basic idea. |
|
|
|
.. _XMLGenerator: http://docs.python.org/dev/library/xml.sax.utils.html#xml.sax.saxutils.XMLGenerator
|
|
|