| 1 |
============================== |
|---|
| 2 |
The syndication feed framework |
|---|
| 3 |
============================== |
|---|
| 4 |
|
|---|
| 5 |
Django comes with a high-level syndication-feed-generating framework that makes |
|---|
| 6 |
creating RSS_ and Atom_ feeds easy. |
|---|
| 7 |
|
|---|
| 8 |
To create any syndication feed, all you have to do is write a short Python |
|---|
| 9 |
class. You can create as many feeds as you want. |
|---|
| 10 |
|
|---|
| 11 |
Django also comes with a lower-level feed-generating API. Use this if you want |
|---|
| 12 |
to generate feeds outside of a Web context, or in some other lower-level way. |
|---|
| 13 |
|
|---|
| 14 |
.. _RSS: http://www.whatisrss.com/ |
|---|
| 15 |
.. _Atom: http://www.atomenabled.org/ |
|---|
| 16 |
|
|---|
| 17 |
The high-level framework |
|---|
| 18 |
======================== |
|---|
| 19 |
|
|---|
| 20 |
Overview |
|---|
| 21 |
-------- |
|---|
| 22 |
|
|---|
| 23 |
The high-level feed-generating framework is a view that's hooked to ``/feeds/`` |
|---|
| 24 |
by default. Django uses the remainder of the URL (everything after ``/feeds/``) |
|---|
| 25 |
to determine which feed to output. |
|---|
| 26 |
|
|---|
| 27 |
To create a feed, just write a ``Feed`` class and point to it in your URLconf_. |
|---|
| 28 |
|
|---|
| 29 |
.. _URLconf: ../url_dispatch/ |
|---|
| 30 |
|
|---|
| 31 |
Initialization |
|---|
| 32 |
-------------- |
|---|
| 33 |
|
|---|
| 34 |
If you're not using the latest Django development version, you'll need to make |
|---|
| 35 |
sure Django's sites framework is installed -- including its database table. |
|---|
| 36 |
(See the `sites framework documentation`_ for more information.) This has |
|---|
| 37 |
changed in the Django development version; the syndication feed framework no |
|---|
| 38 |
longer requires the sites framework. |
|---|
| 39 |
|
|---|
| 40 |
To activate syndication feeds on your Django site, add this line to your |
|---|
| 41 |
URLconf_:: |
|---|
| 42 |
|
|---|
| 43 |
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}), |
|---|
| 44 |
|
|---|
| 45 |
This tells Django to use the RSS framework to handle all URLs starting with |
|---|
| 46 |
``"feeds/"``. (You can change that ``"feeds/"`` prefix to fit your own needs.) |
|---|
| 47 |
|
|---|
| 48 |
This URLconf line has an extra argument: ``{'feed_dict': feeds}``. Use this |
|---|
| 49 |
extra argument to pass the syndication framework the feeds that should be |
|---|
| 50 |
published under that URL. |
|---|
| 51 |
|
|---|
| 52 |
Specifically, ``feed_dict`` should be a dictionary that maps a feed's slug |
|---|
| 53 |
(short URL label) to its ``Feed`` class. |
|---|
| 54 |
|
|---|
| 55 |
You can define the ``feed_dict`` in the URLconf itself. Here's a full example |
|---|
| 56 |
URLconf:: |
|---|
| 57 |
|
|---|
| 58 |
from django.conf.urls.defaults import * |
|---|
| 59 |
from myproject.feeds import LatestEntries, LatestEntriesByCategory |
|---|
| 60 |
|
|---|
| 61 |
feeds = { |
|---|
| 62 |
'latest': LatestEntries, |
|---|
| 63 |
'categories': LatestEntriesByCategory, |
|---|
| 64 |
} |
|---|
| 65 |
|
|---|
| 66 |
urlpatterns = patterns('', |
|---|
| 67 |
# ... |
|---|
| 68 |
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', |
|---|
| 69 |
{'feed_dict': feeds}), |
|---|
| 70 |
# ... |
|---|
| 71 |
) |
|---|
| 72 |
|
|---|
| 73 |
The above example registers two feeds: |
|---|
| 74 |
|
|---|
| 75 |
* The feed represented by ``LatestEntries`` will live at ``feeds/latest/``. |
|---|
| 76 |
* The feed represented by ``LatestEntriesByCategory`` will live at |
|---|
| 77 |
``feeds/categories/``. |
|---|
| 78 |
|
|---|
| 79 |
Once that's set up, you just need to define the ``Feed`` classes themselves. |
|---|
| 80 |
|
|---|
| 81 |
.. _sites framework documentation: ../sites/ |
|---|
| 82 |
.. _URLconf: ../url_dispatch/ |
|---|
| 83 |
.. _settings file: ../settings/ |
|---|
| 84 |
|
|---|
| 85 |
Feed classes |
|---|
| 86 |
------------ |
|---|
| 87 |
|
|---|
| 88 |
A ``Feed`` class is a simple Python class that represents a syndication feed. |
|---|
| 89 |
A feed can be simple (e.g., a "site news" feed, or a basic feed displaying |
|---|
| 90 |
the latest entries of a blog) or more complex (e.g., a feed displaying all the |
|---|
| 91 |
blog entries in a particular category, where the category is variable). |
|---|
| 92 |
|
|---|
| 93 |
``Feed`` classes must subclass ``django.contrib.syndication.feeds.Feed``. They |
|---|
| 94 |
can live anywhere in your codebase. |
|---|
| 95 |
|
|---|
| 96 |
A simple example |
|---|
| 97 |
---------------- |
|---|
| 98 |
|
|---|
| 99 |
This simple example, taken from `chicagocrime.org`_, describes a feed of the |
|---|
| 100 |
latest five news items:: |
|---|
| 101 |
|
|---|
| 102 |
from django.contrib.syndication.feeds import Feed |
|---|
| 103 |
from chicagocrime.models import NewsItem |
|---|
| 104 |
|
|---|
| 105 |
class LatestEntries(Feed): |
|---|
| 106 |
title = "Chicagocrime.org site news" |
|---|
| 107 |
link = "/sitenews/" |
|---|
| 108 |
description = "Updates on changes and additions to chicagocrime.org." |
|---|
| 109 |
|
|---|
| 110 |
def items(self): |
|---|
| 111 |
return NewsItem.objects.order_by('-pub_date')[:5] |
|---|
| 112 |
|
|---|
| 113 |
Note: |
|---|
| 114 |
|
|---|
| 115 |
* The class subclasses ``django.contrib.syndication.feeds.Feed``. |
|---|
| 116 |
* ``title``, ``link`` and ``description`` correspond to the standard |
|---|
| 117 |
RSS ``<title>``, ``<link>`` and ``<description>`` elements, respectively. |
|---|
| 118 |
* ``items()`` is, simply, a method that returns a list of objects that |
|---|
| 119 |
should be included in the feed as ``<item>`` elements. Although this |
|---|
| 120 |
example returns ``NewsItem`` objects using Django's |
|---|
| 121 |
`object-relational mapper`_, ``items()`` doesn't have to return model |
|---|
| 122 |
instances. Although you get a few bits of functionality "for free" by |
|---|
| 123 |
using Django models, ``items()`` can return any type of object you want. |
|---|
| 124 |
* If you're creating an Atom feed, rather than an RSS feed, set the |
|---|
| 125 |
``subtitle`` attribute instead of the ``description`` attribute. See |
|---|
| 126 |
`Publishing Atom and RSS feeds in tandem`_, later, for an example. |
|---|
| 127 |
|
|---|
| 128 |
One thing's left to do. In an RSS feed, each ``<item>`` has a ``<title>``, |
|---|
| 129 |
``<link>`` and ``<description>``. We need to tell the framework what data to |
|---|
| 130 |
put into those elements. |
|---|
| 131 |
|
|---|
| 132 |
* To specify the contents of ``<title>`` and ``<description>``, create |
|---|
| 133 |
`Django templates`_ called ``feeds/latest_title.html`` and |
|---|
| 134 |
``feeds/latest_description.html``, where ``latest`` is the ``slug`` |
|---|
| 135 |
specified in the URLconf for the given feed. Note the ``.html`` extension |
|---|
| 136 |
is required. The RSS system renders that template for each item, passing |
|---|
| 137 |
it two template context variables: |
|---|
| 138 |
|
|---|
| 139 |
* ``{{ obj }}`` -- The current object (one of whichever objects you |
|---|
| 140 |
returned in ``items()``). |
|---|
| 141 |
* ``{{ site }}`` -- A ``django.contrib.sites.models.Site`` object |
|---|
| 142 |
representing the current site. This is useful for |
|---|
| 143 |
``{{ site.domain }}`` or ``{{ site.name }}``. Note that if you're |
|---|
| 144 |
using the latest Django development version and do *not* have the |
|---|
| 145 |
Django sites framework installed, this will be set to a |
|---|
| 146 |
``django.contrib.sites.models.RequestSite`` object. See the |
|---|
| 147 |
`RequestSite section of the sites framework documentation`_ for |
|---|
| 148 |
more. |
|---|
| 149 |
|
|---|
| 150 |
If you don't create a template for either the title or description, the |
|---|
| 151 |
framework will use the template ``"{{ obj }}"`` by default -- that is, |
|---|
| 152 |
the normal string representation of the object. You can also change the |
|---|
| 153 |
names of these two templates by specifying ``title_template`` and |
|---|
| 154 |
``description_template`` as attributes of your ``Feed`` class. |
|---|
| 155 |
* To specify the contents of ``<link>``, you have two options. For each |
|---|
| 156 |
item in ``items()``, Django first tries executing a |
|---|
| 157 |
``get_absolute_url()`` method on that object. If that method doesn't |
|---|
| 158 |
exist, it tries calling a method ``item_link()`` in the ``Feed`` class, |
|---|
| 159 |
passing it a single parameter, ``item``, which is the object itself. |
|---|
| 160 |
Both ``get_absolute_url()`` and ``item_link()`` should return the item's |
|---|
| 161 |
URL as a normal Python string. As with ``get_absolute_url()``, the |
|---|
| 162 |
result of ``item_link()`` will be included directly in the URL, so you |
|---|
| 163 |
are responsible for doing all necessary URL quoting and conversion to |
|---|
| 164 |
ASCII inside the method itself. |
|---|
| 165 |
|
|---|
| 166 |
* For the LatestEntries example above, we could have very simple feed templates: |
|---|
| 167 |
|
|---|
| 168 |
* latest_title.html:: |
|---|
| 169 |
|
|---|
| 170 |
{{ obj.title }} |
|---|
| 171 |
|
|---|
| 172 |
* latest_description.html:: |
|---|
| 173 |
|
|---|
| 174 |
{{ obj.description }} |
|---|
| 175 |
|
|---|
| 176 |
.. _chicagocrime.org: http://www.chicagocrime.org/ |
|---|
| 177 |
.. _object-relational mapper: ../db-api/ |
|---|
| 178 |
.. _Django templates: ../templates/ |
|---|
| 179 |
.. _RequestSite section of the sites framework documentation: ../sites/#requestsite-objects |
|---|
| 180 |
|
|---|
| 181 |
A complex example |
|---|
| 182 |
----------------- |
|---|
| 183 |
|
|---|
| 184 |
The framework also supports more complex feeds, via parameters. |
|---|
| 185 |
|
|---|
| 186 |
For example, `chicagocrime.org`_ offers an RSS feed of recent crimes for every |
|---|
| 187 |
police beat in Chicago. It'd be silly to create a separate ``Feed`` class for |
|---|
| 188 |
each police beat; that would violate the `DRY principle`_ and would couple data |
|---|
| 189 |
to programming logic. Instead, the syndication framework lets you make generic |
|---|
| 190 |
feeds that output items based on information in the feed's URL. |
|---|
| 191 |
|
|---|
| 192 |
On chicagocrime.org, the police-beat feeds are accessible via URLs like this: |
|---|
| 193 |
|
|---|
| 194 |
* ``/rss/beats/0613/`` -- Returns recent crimes for beat 0613. |
|---|
| 195 |
* ``/rss/beats/1424/`` -- Returns recent crimes for beat 1424. |
|---|
| 196 |
|
|---|
| 197 |
The slug here is ``"beats"``. The syndication framework sees the extra URL bits |
|---|
| 198 |
after the slug -- ``0613`` and ``1424`` -- and gives you a hook to tell it what |
|---|
| 199 |
those URL bits mean, and how they should influence which items get published in |
|---|
| 200 |
the feed. |
|---|
| 201 |
|
|---|
| 202 |
An example makes this clear. Here's the code for these beat-specific feeds:: |
|---|
| 203 |
|
|---|
| 204 |
from django.contrib.syndication.feeds import FeedDoesNotExist |
|---|
| 205 |
|
|---|
| 206 |
class BeatFeed(Feed): |
|---|
| 207 |
def get_object(self, bits): |
|---|
| 208 |
# In case of "/rss/beats/0613/foo/bar/baz/", or other such clutter, |
|---|
| 209 |
# check that bits has only one member. |
|---|
| 210 |
if len(bits) != 1: |
|---|
| 211 |
raise ObjectDoesNotExist |
|---|
| 212 |
return Beat.objects.get(beat__exact=bits[0]) |
|---|
| 213 |
|
|---|
| 214 |
def title(self, obj): |
|---|
| 215 |
return "Chicagocrime.org: Crimes for beat %s" % obj.beat |
|---|
| 216 |
|
|---|
| 217 |
def link(self, obj): |
|---|
| 218 |
if not obj: |
|---|
| 219 |
raise FeedDoesNotExist |
|---|
| 220 |
return obj.get_absolute_url() |
|---|
| 221 |
|
|---|
| 222 |
def description(self, obj): |
|---|
| 223 |
return "Crimes recently reported in police beat %s" % obj.beat |
|---|
| 224 |
|
|---|
| 225 |
def items(self, obj): |
|---|
| 226 |
return Crime.objects.filter(beat__id__exact=obj.id).order_by('-crime_date')[:30] |
|---|
| 227 |
|
|---|
| 228 |
Here's the basic algorithm the RSS framework follows, given this class and a |
|---|
| 229 |
request to the URL ``/rss/beats/0613/``: |
|---|
| 230 |
|
|---|
| 231 |
* The framework gets the URL ``/rss/beats/0613/`` and notices there's |
|---|
| 232 |
an extra bit of URL after the slug. It splits that remaining string by |
|---|
| 233 |
the slash character (``"/"``) and calls the ``Feed`` class' |
|---|
| 234 |
``get_object()`` method, passing it the bits. In this case, bits is |
|---|
| 235 |
``['0613']``. For a request to ``/rss/beats/0613/foo/bar/``, bits would |
|---|
| 236 |
be ``['0613', 'foo', 'bar']``. |
|---|
| 237 |
|
|---|
| 238 |
* ``get_object()`` is responsible for retrieving the given beat, from the |
|---|
| 239 |
given ``bits``. In this case, it uses the Django database API to retrieve |
|---|
| 240 |
the beat. Note that ``get_object()`` should raise |
|---|
| 241 |
``django.core.exceptions.ObjectDoesNotExist`` if given invalid |
|---|
| 242 |
parameters. There's no ``try``/``except`` around the |
|---|
| 243 |
``Beat.objects.get()`` call, because it's not necessary; that function |
|---|
| 244 |
raises ``Beat.DoesNotExist`` on failure, and ``Beat.DoesNotExist`` is a |
|---|
| 245 |
subclass of ``ObjectDoesNotExist``. Raising ``ObjectDoesNotExist`` in |
|---|
| 246 |
``get_object()`` tells Django to produce a 404 error for that request. |
|---|
| 247 |
|
|---|
| 248 |
**New in Django development version:** The ``get_object()`` method also |
|---|
| 249 |
has a chance to handle the ``/rss/beats/`` url. In this case, ``bits`` |
|---|
| 250 |
will be an empty list. In our example, ``len(bits) != 1`` and an |
|---|
| 251 |
``ObjectDoesNotExist`` exception will be raised, so ``/rss/beats/`` will |
|---|
| 252 |
generate a 404 page. But you can handle this case however you like. For |
|---|
| 253 |
example, you could generate a combined feed for all beats. |
|---|
| 254 |
|
|---|
| 255 |
* To generate the feed's ``<title>``, ``<link>`` and ``<description>``, |
|---|
| 256 |
Django uses the ``title()``, ``link()`` and ``description()`` methods. In |
|---|
| 257 |
the previous example, they were simple string class attributes, but this |
|---|
| 258 |
example illustrates that they can be either strings *or* methods. For |
|---|
| 259 |
each of ``title``, ``link`` and ``description``, Django follows this |
|---|
| 260 |
algorithm: |
|---|
| 261 |
|
|---|
| 262 |
* First, it tries to call a method, passing the ``obj`` argument, |
|---|
| 263 |
where ``obj`` is the object returned by ``get_object()``. |
|---|
| 264 |
* Failing that, it tries to call a method with no arguments. |
|---|
| 265 |
* Failing that, it uses the class attribute. |
|---|
| 266 |
|
|---|
| 267 |
Inside the ``link()`` method, we handle the possibility that ``obj`` |
|---|
| 268 |
might be ``None``, which can occur when the URL isn't fully specified. In |
|---|
| 269 |
some cases, you might want to do something else in this case, which would |
|---|
| 270 |
mean you'd need to check for ``obj`` existing in other methods as well. |
|---|
| 271 |
(The ``link()`` method is called very early in the feed generation |
|---|
| 272 |
process, so it's a good place to bail out early.) |
|---|
| 273 |
|
|---|
| 274 |
* Finally, note that ``items()`` in this example also takes the ``obj`` |
|---|
| 275 |
argument. The algorithm for ``items`` is the same as described in the |
|---|
| 276 |
previous step -- first, it tries ``items(obj)``, then ``items()``, then |
|---|
| 277 |
finally an ``items`` class attribute (which should be a list). |
|---|
| 278 |
|
|---|
| 279 |
The ``ExampleFeed`` class below gives full documentation on methods and |
|---|
| 280 |
attributes of ``Feed`` classes. |
|---|
| 281 |
|
|---|
| 282 |
.. _DRY principle: http://c2.com/cgi/wiki?DontRepeatYourself |
|---|
| 283 |
|
|---|
| 284 |
Specifying the type of feed |
|---|
| 285 |
--------------------------- |
|---|
| 286 |
|
|---|
| 287 |
By default, feeds produced in this framework use RSS 2.0. |
|---|
| 288 |
|
|---|
| 289 |
To change that, add a ``feed_type`` attribute to your ``Feed`` class, like so:: |
|---|
| 290 |
|
|---|
| 291 |
from django.utils.feedgenerator import Atom1Feed |
|---|
| 292 |
|
|---|
| 293 |
class MyFeed(Feed): |
|---|
| 294 |
feed_type = Atom1Feed |
|---|
| 295 |
|
|---|
| 296 |
Note that you set ``feed_type`` to a class object, not an instance. |
|---|
| 297 |
|
|---|
| 298 |
Currently available feed types are: |
|---|
| 299 |
|
|---|
| 300 |
* ``django.utils.feedgenerator.Rss201rev2Feed`` (RSS 2.01. Default.) |
|---|
| 301 |
* ``django.utils.feedgenerator.RssUserland091Feed`` (RSS 0.91.) |
|---|
| 302 |
* ``django.utils.feedgenerator.Atom1Feed`` (Atom 1.0.) |
|---|
| 303 |
|
|---|
| 304 |
Enclosures |
|---|
| 305 |
---------- |
|---|
| 306 |
|
|---|
| 307 |
To specify enclosures, such as those used in creating podcast feeds, use the |
|---|
| 308 |
``item_enclosure_url``, ``item_enclosure_length`` and |
|---|
| 309 |
``item_enclosure_mime_type`` hooks. See the ``ExampleFeed`` class below for |
|---|
| 310 |
usage examples. |
|---|
| 311 |
|
|---|
| 312 |
Language |
|---|
| 313 |
-------- |
|---|
| 314 |
|
|---|
| 315 |
Feeds created by the syndication framework automatically include the |
|---|
| 316 |
appropriate ``<language>`` tag (RSS 2.0) or ``xml:lang`` attribute (Atom). This |
|---|
| 317 |
comes directly from your `LANGUAGE_CODE setting`_. |
|---|
| 318 |
|
|---|
| 319 |
.. _LANGUAGE_CODE setting: ../settings/#language-code |
|---|
| 320 |
|
|---|
| 321 |
URLs |
|---|
| 322 |
---- |
|---|
| 323 |
|
|---|
| 324 |
The ``link`` method/attribute can return either an absolute URL (e.g. |
|---|
| 325 |
``"/blog/"``) or a URL with the fully-qualified domain and protocol (e.g. |
|---|
| 326 |
``"http://www.example.com/blog/"``). If ``link`` doesn't return the domain, |
|---|
| 327 |
the syndication framework will insert the domain of the current site, according |
|---|
| 328 |
to your `SITE_ID setting`_. |
|---|
| 329 |
|
|---|
| 330 |
Atom feeds require a ``<link rel="self">`` that defines the feed's current |
|---|
| 331 |
location. The syndication framework populates this automatically, using the |
|---|
| 332 |
domain of the current site according to the SITE_ID setting. |
|---|
| 333 |
|
|---|
| 334 |
.. _SITE_ID setting: ../settings/#site-id |
|---|
| 335 |
|
|---|
| 336 |
Publishing Atom and RSS feeds in tandem |
|---|
| 337 |
--------------------------------------- |
|---|
| 338 |
|
|---|
| 339 |
Some developers like to make available both Atom *and* RSS versions of their |
|---|
| 340 |
feeds. That's easy to do with Django: Just create a subclass of your ``Feed`` |
|---|
| 341 |
class and set the ``feed_type`` to something different. Then update your |
|---|
| 342 |
URLconf to add the extra versions. |
|---|
| 343 |
|
|---|
| 344 |
Here's a full example:: |
|---|
| 345 |
|
|---|
| 346 |
from django.contrib.syndication.feeds import Feed |
|---|
| 347 |
from chicagocrime.models import NewsItem |
|---|
| 348 |
from django.utils.feedgenerator import Atom1Feed |
|---|
| 349 |
|
|---|
| 350 |
class RssSiteNewsFeed(Feed): |
|---|
| 351 |
title = "Chicagocrime.org site news" |
|---|
| 352 |
link = "/sitenews/" |
|---|
| 353 |
description = "Updates on changes and additions to chicagocrime.org." |
|---|
| 354 |
|
|---|
| 355 |
def items(self): |
|---|
| 356 |
return NewsItem.objects.order_by('-pub_date')[:5] |
|---|
| 357 |
|
|---|
| 358 |
class AtomSiteNewsFeed(RssSiteNewsFeed): |
|---|
| 359 |
feed_type = Atom1Feed |
|---|
| 360 |
subtitle = RssSiteNewsFeed.description |
|---|
| 361 |
|
|---|
| 362 |
.. Note:: |
|---|
| 363 |
In this example, the RSS feed uses a ``description`` while the Atom feed |
|---|
| 364 |
uses a ``subtitle``. That's because Atom feeds don't provide for a |
|---|
| 365 |
feed-level "description," but they *do* provide for a "subtitle." |
|---|
| 366 |
|
|---|
| 367 |
If you provide a ``description`` in your ``Feed`` class, Django will *not* |
|---|
| 368 |
automatically put that into the ``subtitle`` element, because a subtitle |
|---|
| 369 |
and description are not necessarily the same thing. Instead, you should |
|---|
| 370 |
define a ``subtitle`` attribute. |
|---|
| 371 |
|
|---|
| 372 |
In the above example, we simply set the Atom feed's ``subtitle`` to the |
|---|
| 373 |
RSS feed's ``description``, because it's quite short already. |
|---|
| 374 |
|
|---|
| 375 |
And the accompanying URLconf:: |
|---|
| 376 |
|
|---|
| 377 |
from django.conf.urls.defaults import * |
|---|
| 378 |
from myproject.feeds import RssSiteNewsFeed, AtomSiteNewsFeed |
|---|
| 379 |
|
|---|
| 380 |
feeds = { |
|---|
| 381 |
'rss': RssSiteNewsFeed, |
|---|
| 382 |
'atom': AtomSiteNewsFeed, |
|---|
| 383 |
} |
|---|
| 384 |
|
|---|
| 385 |
urlpatterns = patterns('', |
|---|
| 386 |
# ... |
|---|
| 387 |
(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', |
|---|
| 388 |
{'feed_dict': feeds}), |
|---|
| 389 |
# ... |
|---|
| 390 |
) |
|---|
| 391 |
|
|---|
| 392 |
Feed class reference |
|---|
| 393 |
-------------------- |
|---|
| 394 |
|
|---|
| 395 |
This example illustrates all possible attributes and methods for a ``Feed`` class:: |
|---|
| 396 |
|
|---|
| 397 |
from django.contrib.syndication.feeds import Feed |
|---|
| 398 |
from django.utils import feedgenerator |
|---|
| 399 |
|
|---|
| 400 |
class ExampleFeed(Feed): |
|---|
| 401 |
|
|---|
| 402 |
# FEED TYPE -- Optional. This should be a class that subclasses |
|---|
| 403 |
# django.utils.feedgenerator.SyndicationFeed. This designates which |
|---|
| 404 |
# type of feed this should be: RSS 2.0, Atom 1.0, etc. |
|---|
| 405 |
# If you don't specify feed_type, your feed will be RSS 2.0. |
|---|
| 406 |
# This should be a class, not an instance of the class. |
|---|
| 407 |
|
|---|
| 408 |
feed_type = feedgenerator.Rss201rev2Feed |
|---|
| 409 |
|
|---|
| 410 |
# TEMPLATE NAMES -- Optional. These should be strings representing |
|---|
| 411 |
# names of Django templates that the system should use in rendering the |
|---|
| 412 |
# title and description of your feed items. Both are optional. |
|---|
| 413 |
# If you don't specify one, or either, Django will use the template |
|---|
| 414 |
# 'feeds/SLUG_title.html' and 'feeds/SLUG_description.html', where SLUG |
|---|
| 415 |
# is the slug you specify in the URL. |
|---|
| 416 |
|
|---|
| 417 |
title_template = None |
|---|
| 418 |
description_template = None |
|---|
| 419 |
|
|---|
| 420 |
# TITLE -- One of the following three is required. The framework looks |
|---|
| 421 |
# for them in this order. |
|---|
| 422 |
|
|---|
| 423 |
def title(self, obj): |
|---|
| 424 |
""" |
|---|
| 425 |
Takes the object returned by get_object() and returns the feed's |
|---|
| 426 |
title as a normal Python string. |
|---|
| 427 |
""" |
|---|
| 428 |
|
|---|
| 429 |
def title(self): |
|---|
| 430 |
""" |
|---|
| 431 |
Returns the feed's title as a normal Python string. |
|---|
| 432 |
""" |
|---|
| 433 |
|
|---|
| 434 |
title = 'foo' # Hard-coded title. |
|---|
| 435 |
|
|---|
| 436 |
# LINK -- One of the following three is required. The framework looks |
|---|
| 437 |
# for them in this order. |
|---|
| 438 |
|
|---|
| 439 |
def link(self, obj): |
|---|
| 440 |
""" |
|---|
| 441 |
Takes the object returned by get_object() and returns the feed's |
|---|
| 442 |
link as a normal Python string. |
|---|
| 443 |
""" |
|---|
| 444 |
|
|---|
| 445 |
def link(self): |
|---|
| 446 |
""" |
|---|
| 447 |
Returns the feed's link as a normal Python string. |
|---|
| 448 |
""" |
|---|
| 449 |
|
|---|
| 450 |
link = '/foo/bar/' # Hard-coded link. |
|---|
| 451 |
|
|---|
| 452 |
# GUID -- One of the following three is optional. The framework looks |
|---|
| 453 |
# for them in this order. This property is only used for Atom feeds |
|---|
| 454 |
# (where it is the feed-level ID element). If not provided, the feed |
|---|
| 455 |
# link is used as the ID. |
|---|
| 456 |
# |
|---|
| 457 |
# (New in Django development version) |
|---|
| 458 |
|
|---|
| 459 |
def feed_guid(self, obj): |
|---|
| 460 |
""" |
|---|
| 461 |
Takes the object returned by get_object() and returns the globally |
|---|
| 462 |
unique ID for the feed as a normal Python string. |
|---|
| 463 |
""" |
|---|
| 464 |
|
|---|
| 465 |
def feed_guid(self): |
|---|
| 466 |
""" |
|---|
| 467 |
Returns the feed's globally unique ID as a normal Python string. |
|---|
| 468 |
""" |
|---|
| 469 |
|
|---|
| 470 |
feed_guid = '/foo/bar/1234' # Hard-coded guid. |
|---|
| 471 |
|
|---|
| 472 |
# DESCRIPTION -- One of the following three is required. The framework |
|---|
| 473 |
# looks for them in this order. |
|---|
| 474 |
|
|---|
| 475 |
def description(self, obj): |
|---|
| 476 |
""" |
|---|
| 477 |
Takes the object returned by get_object() and returns the feed's |
|---|
| 478 |
description as a normal Python string. |
|---|
| 479 |
""" |
|---|
| 480 |
|
|---|
| 481 |
def description(self): |
|---|
| 482 |
""" |
|---|
| 483 |
Returns the feed's description as a normal Python string. |
|---|
| 484 |
""" |
|---|
| 485 |
|
|---|
| 486 |
description = 'Foo bar baz.' # Hard-coded description. |
|---|
| 487 |
|
|---|
| 488 |
# AUTHOR NAME --One of the following three is optional. The framework |
|---|
| 489 |
# looks for them in this order. |
|---|
| 490 |
|
|---|
| 491 |
def author_name(self, obj): |
|---|
| 492 |
""" |
|---|
| 493 |
Takes the object returned by get_object() and returns the feed's |
|---|
| 494 |
author's name as a normal Python string. |
|---|
| 495 |
""" |
|---|
| 496 |
|
|---|
| 497 |
def author_name(self): |
|---|
| 498 |
""" |
|---|
| 499 |
Returns the feed's author's name as a normal Python string. |
|---|
| 500 |
""" |
|---|
| 501 |
|
|---|
| 502 |
author_name = 'Sally Smith' # Hard-coded author name. |
|---|
| 503 |
|
|---|
| 504 |
# AUTHOR E-MAIL --One of the following three is optional. The framework |
|---|
| 505 |
# looks for them in this order. |
|---|
| 506 |
|
|---|
| 507 |
def author_email(self, obj): |
|---|
| 508 |
""" |
|---|
| 509 |
Takes the object returned by get_object() and returns the feed's |
|---|
| 510 |
author's e-mail as a normal Python string. |
|---|
| 511 |
""" |
|---|
| 512 |
|
|---|
| 513 |
def author_email(self): |
|---|
| 514 |
""" |
|---|
| 515 |
Returns the feed's author's e-mail as a normal Python string. |
|---|
| 516 |
""" |
|---|
| 517 |
|
|---|
| 518 |
author_email = 'test@example.com' # Hard-coded author e-mail. |
|---|
| 519 |
|
|---|
| 520 |
# AUTHOR LINK --One of the following three is optional. The framework |
|---|
| 521 |
# looks for them in this order. In each case, the URL should include |
|---|
| 522 |
# the "http://" and domain name. |
|---|
| 523 |
|
|---|
| 524 |
def author_link(self, obj): |
|---|
| 525 |
""" |
|---|
| 526 |
Takes the object returned by get_object() and returns the feed's |
|---|
| 527 |
author's URL as a normal Python string. |
|---|
| 528 |
""" |
|---|
| 529 |
|
|---|
| 530 |
def author_link(self): |
|---|
| 531 |
""" |
|---|
| 532 |
Returns the feed's author's URL as a normal Python string. |
|---|
| 533 |
""" |
|---|
| 534 |
|
|---|
| 535 |
author_link = 'http://www.example.com/' # Hard-coded author URL. |
|---|
| 536 |
|
|---|
| 537 |
# CATEGORIES -- One of the following three is optional. The framework |
|---|
| 538 |
# looks for them in this order. In each case, the method/attribute |
|---|
| 539 |
# should return an iterable object that returns strings. |
|---|
| 540 |
|
|---|
| 541 |
def categories(self, obj): |
|---|
| 542 |
""" |
|---|
| 543 |
Takes the object returned by get_object() and returns the feed's |
|---|
| 544 |
categories as iterable over strings. |
|---|
| 545 |
""" |
|---|
| 546 |
|
|---|
| 547 |
def categories(self): |
|---|
| 548 |
""" |
|---|
| 549 |
Returns the feed's categories as iterable over strings. |
|---|
| 550 |
""" |
|---|
| 551 |
|
|---|
| 552 |
categories = ("python", "django") # Hard-coded list of categories. |
|---|
| 553 |
|
|---|
| 554 |
# COPYRIGHT NOTICE -- One of the following three is optional. The |
|---|
| 555 |
# framework looks for them in this order. |
|---|
| 556 |
|
|---|
| 557 |
def copyright(self, obj): |
|---|
| 558 |
""" |
|---|
| 559 |
Takes the object returned by get_object() and returns the feed's |
|---|
| 560 |
copyright notice as a normal Python string. |
|---|
| 561 |
""" |
|---|
| 562 |
|
|---|
| 563 |
def copyright(self): |
|---|
| 564 |
""" |
|---|
| 565 |
Returns the feed's copyright notice as a normal Python string. |
|---|
| 566 |
""" |
|---|
| 567 |
|
|---|
| 568 |
copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice. |
|---|
| 569 |
|
|---|
| 570 |
# TTL -- One of the following three is optional. The framework looks |
|---|
| 571 |
# for them in this order. Ignored for Atom feeds. |
|---|
| 572 |
|
|---|
| 573 |
def ttl(self, obj): |
|---|
| 574 |
""" |
|---|
| 575 |
Takes the object returned by get_object() and returns the feed's |
|---|
| 576 |
TTL (Time To Live) as a normal Python string. |
|---|
| 577 |
""" |
|---|
| 578 |
|
|---|
| 579 |
def ttl(self): |
|---|
| 580 |
""" |
|---|
| 581 |
Returns the feed's TTL as a normal Python string. |
|---|
| 582 |
""" |
|---|
| 583 |
|
|---|
| 584 |
ttl = 600 # Hard-coded Time To Live. |
|---|
| 585 |
|
|---|
| 586 |
# ITEMS -- One of the following three is required. The framework looks |
|---|
| 587 |
# for them in this order. |
|---|
| 588 |
|
|---|
| 589 |
def items(self, obj): |
|---|
| 590 |
""" |
|---|
| 591 |
Takes the object returned by get_object() and returns a list of |
|---|
| 592 |
items to publish in this feed. |
|---|
| 593 |
""" |
|---|
| 594 |
|
|---|
| 595 |
def items(self): |
|---|
| 596 |
""" |
|---|
| 597 |
Returns a list of items to publish in this feed. |
|---|
| 598 |
""" |
|---|
| 599 |
|
|---|
| 600 |
items = ('Item 1', 'Item 2') # Hard-coded items. |
|---|
| 601 |
|
|---|
| 602 |
# GET_OBJECT -- This is required for feeds that publish different data |
|---|
| 603 |
# for different URL parameters. (See "A complex example" above.) |
|---|
| 604 |
|
|---|
| 605 |
def get_object(self, bits): |
|---|
| 606 |
""" |
|---|
| 607 |
Takes a list of strings gleaned from the URL and returns an object |
|---|
| 608 |
represented by this feed. Raises |
|---|
| 609 |
django.core.exceptions.ObjectDoesNotExist on error. |
|---|
| 610 |
""" |
|---|
| 611 |
|
|---|
| 612 |
# ITEM LINK -- One of these three is required. The framework looks for |
|---|
| 613 |
# them in this order. |
|---|
| 614 |
|
|---|
| 615 |
# First, the framework tries the two methods below, in |
|---|
| 616 |
# order. Failing that, it falls back to the get_absolute_url() |
|---|
| 617 |
# method on each item returned by items(). |
|---|
| 618 |
|
|---|
| 619 |
def item_link(self, item): |
|---|
| 620 |
""" |
|---|
| 621 |
Takes an item, as returned by items(), and returns the item's URL. |
|---|
| 622 |
""" |
|---|
| 623 |
|
|---|
| 624 |
def item_link(self): |
|---|
| 625 |
""" |
|---|
| 626 |
Returns the URL for every item in the feed. |
|---|
| 627 |
""" |
|---|
| 628 |
|
|---|
| 629 |
# ITEM_GUID -- The following method is optional. This property is |
|---|
| 630 |
# only used for Atom feeds (it is the ID element for an item in an |
|---|
| 631 |
# Atom feed). If not provided, the item's link is used by default. |
|---|
| 632 |
# |
|---|
| 633 |
# (New in Django development version) |
|---|
| 634 |
|
|---|
| 635 |
def item_guid(self, obj): |
|---|
| 636 |
""" |
|---|
| 637 |
Takes an item, as return by items(), and returns the item's ID. |
|---|
| 638 |
""" |
|---|
| 639 |
|
|---|
| 640 |
# ITEM AUTHOR NAME -- One of the following three is optional. The |
|---|
| 641 |
# framework looks for them in this order. |
|---|
| 642 |
|
|---|
| 643 |
def item_author_name(self, item): |
|---|
| 644 |
""" |
|---|
| 645 |
Takes an item, as returned by items(), and returns the item's |
|---|
| 646 |
author's name as a normal Python string. |
|---|
| 647 |
""" |
|---|
| 648 |
|
|---|
| 649 |
def item_author_name(self): |
|---|
| 650 |
""" |
|---|
| 651 |
Returns the author name for every item in the feed. |
|---|
| 652 |
""" |
|---|
| 653 |
|
|---|
| 654 |
item_author_name = 'Sally Smith' # Hard-coded author name. |
|---|
| 655 |
|
|---|
| 656 |
# ITEM AUTHOR E-MAIL --One of the following three is optional. The |
|---|
| 657 |
# framework looks for them in this order. |
|---|
| 658 |
# |
|---|
| 659 |
# If you specify this, you must specify item_author_name. |
|---|
| 660 |
|
|---|
| 661 |
def item_author_email(self, obj): |
|---|
| 662 |
""" |
|---|
| 663 |
Takes an item, as returned by items(), and returns the item's |
|---|
| 664 |
author's e-mail as a normal Python string. |
|---|
| 665 |
""" |
|---|
| 666 |
|
|---|
| 667 |
def item_author_email(self): |
|---|
| 668 |
""" |
|---|
| 669 |
Returns the author e-mail for every item in the feed. |
|---|
| 670 |
""" |
|---|
| 671 |
|
|---|
| 672 |
item_author_email = 'test@example.com' # Hard-coded author e-mail. |
|---|
| 673 |
|
|---|
| 674 |
# ITEM AUTHOR LINK --One of the following three is optional. The |
|---|
| 675 |
# framework looks for them in this order. In each case, the URL should |
|---|
| 676 |
# include the "http://" and domain name. |
|---|
| 677 |
# |
|---|
| 678 |
# If you specify this, you must specify item_author_name. |
|---|
| 679 |
|
|---|
| 680 |
def item_author_link(self, obj): |
|---|
| 681 |
""" |
|---|
| 682 |
Takes an item, as returned by items(), and returns the item's |
|---|
| 683 |
author's URL as a normal Python string. |
|---|
| 684 |
""" |
|---|
| 685 |
|
|---|
| 686 |
def item_author_link(self): |
|---|
| 687 |
""" |
|---|
| 688 |
Returns the author URL for every item in the feed. |
|---|
| 689 |
""" |
|---|
| 690 |
|
|---|
| 691 |
item_author_link = 'http://www.example.com/' # Hard-coded author URL. |
|---|
| 692 |
|
|---|
| 693 |
# ITEM ENCLOSURE URL -- One of these three is required if you're |
|---|
| 694 |
# publishing enclosures. The framework looks for them in this order. |
|---|
| 695 |
|
|---|
| 696 |
def item_enclosure_url(self, item): |
|---|
| 697 |
""" |
|---|
| 698 |
Takes an item, as returned by items(), and returns the item's |
|---|
| 699 |
enclosure URL. |
|---|
| 700 |
""" |
|---|
| 701 |
|
|---|
| 702 |
def item_enclosure_url(self): |
|---|
| 703 |
""" |
|---|
| 704 |
Returns the enclosure URL for every item in the feed. |
|---|
| 705 |
""" |
|---|
| 706 |
|
|---|
| 707 |
item_enclosure_url = "/foo/bar.mp3" # Hard-coded enclosure link. |
|---|
| 708 |
|
|---|
| 709 |
# ITEM ENCLOSURE LENGTH -- One of these three is required if you're |
|---|
| 710 |
# publishing enclosures. The framework looks for them in this order. |
|---|
| 711 |
# In each case, the returned value should be either an integer, or a |
|---|
| 712 |
# string representation of the integer, in bytes. |
|---|
| 713 |
|
|---|
| 714 |
def item_enclosure_length(self, item): |
|---|
| 715 |
""" |
|---|
| 716 |
Takes an item, as returned by items(), and returns the item's |
|---|
| 717 |
enclosure length. |
|---|
| 718 |
""" |
|---|
| 719 |
|
|---|
| 720 |
def item_enclosure_length(self): |
|---|
| 721 |
""" |
|---|
| 722 |
Returns the enclosure length for every item in the feed. |
|---|
| 723 |
""" |
|---|
| 724 |
|
|---|
| 725 |
item_enclosure_length = 32000 # Hard-coded enclosure length. |
|---|
| 726 |
|
|---|
| 727 |
# ITEM ENCLOSURE MIME TYPE -- One of these three is required if you're |
|---|
| 728 |
# publishing enclosures. The framework looks for them in this order. |
|---|
| 729 |
|
|---|
| 730 |
def item_enclosure_mime_type(self, item): |
|---|
| 731 |
""" |
|---|
| 732 |
Takes an item, as returned by items(), and returns the item's |
|---|
| 733 |
enclosure MIME type. |
|---|
| 734 |
""" |
|---|
| 735 |
|
|---|
| 736 |
def item_enclosure_mime_type(self): |
|---|
| 737 |
""" |
|---|
| 738 |
Returns the enclosure MIME type for every item in the feed. |
|---|
| 739 |
""" |
|---|
| 740 |
|
|---|
| 741 |
item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure MIME type. |
|---|
| 742 |
|
|---|
| 743 |
# ITEM PUBDATE -- It's optional to use one of these three. This is a |
|---|
| 744 |
# hook that specifies how to get the pubdate for a given item. |
|---|
| 745 |
# In each case, the method/attribute should return a Python |
|---|
| 746 |
# datetime.datetime object. |
|---|
| 747 |
|
|---|
| 748 |
def item_pubdate(self, item): |
|---|
| 749 |
""" |
|---|
| 750 |
Takes an item, as returned by items(), and returns the item's |
|---|
| 751 |
pubdate. |
|---|
| 752 |
""" |
|---|
| 753 |
|
|---|
| 754 |
def item_pubdate(self): |
|---|
| 755 |
""" |
|---|
| 756 |
Returns the pubdate for every item in the feed. |
|---|
| 757 |
""" |
|---|
| 758 |
|
|---|
| 759 |
item_pubdate = datetime.datetime(2005, 5, 3) # Hard-coded pubdate. |
|---|
| 760 |
|
|---|
| 761 |
# ITEM CATEGORIES -- It's optional to use one of these three. This is |
|---|
| 762 |
# a hook that specifies how to get the list of categories for a given |
|---|
| 763 |
# item. In each case, the method/attribute should return an iterable |
|---|
| 764 |
# object that returns strings. |
|---|
| 765 |
|
|---|
| 766 |
def item_categories(self, item): |
|---|
| 767 |
""" |
|---|
| 768 |
Takes an item, as returned by items(), and returns the item's |
|---|
| 769 |
categories. |
|---|
| 770 |
""" |
|---|
| 771 |
|
|---|
| 772 |
def item_categories(self): |
|---|
| 773 |
""" |
|---|
| 774 |
Returns the categories for every item in the feed. |
|---|
| 775 |
""" |
|---|
| 776 |
|
|---|
| 777 |
item_categories = ("python", "django") # Hard-coded categories. |
|---|
| 778 |
|
|---|
| 779 |
# ITEM COPYRIGHT NOTICE (only applicable to Atom feeds) -- One of the |
|---|
| 780 |
# following three is optional. The framework looks for them in this |
|---|
| 781 |
# order. |
|---|
| 782 |
|
|---|
| 783 |
def item_copyright(self, obj): |
|---|
| 784 |
""" |
|---|
| 785 |
Takes an item, as returned by items(), and returns the item's |
|---|
| 786 |
copyright notice as a normal Python string. |
|---|
| 787 |
""" |
|---|
| 788 |
|
|---|
| 789 |
def item_copyright(self): |
|---|
| 790 |
""" |
|---|
| 791 |
Returns the copyright notice for every item in the feed. |
|---|
| 792 |
""" |
|---|
| 793 |
|
|---|
| 794 |
item_copyright = 'Copyright (c) 2007, Sally Smith' # Hard-coded copyright notice. |
|---|
| 795 |
|
|---|
| 796 |
|
|---|
| 797 |
The low-level framework |
|---|
| 798 |
======================= |
|---|
| 799 |
|
|---|
| 800 |
Behind the scenes, the high-level RSS framework uses a lower-level framework |
|---|
| 801 |
for generating feeds' XML. This framework lives in a single module: |
|---|
| 802 |
`django/utils/feedgenerator.py`_. |
|---|
| 803 |
|
|---|
| 804 |
Feel free to use this framework on your own, for lower-level tasks. |
|---|
| 805 |
|
|---|
| 806 |
The ``feedgenerator`` module contains a base class ``SyndicationFeed`` and |
|---|
| 807 |
several subclasses: |
|---|
| 808 |
|
|---|
| 809 |
* ``RssUserland091Feed`` |
|---|
| 810 |
* ``Rss201rev2Feed`` |
|---|
| 811 |
* ``Atom1Feed`` |
|---|
| 812 |
|
|---|
| 813 |
Each of these three classes knows how to render a certain type of feed as XML. |
|---|
| 814 |
They share this interface: |
|---|
| 815 |
|
|---|
| 816 |
``__init__(title, link, description, language=None, author_email=None,`` |
|---|
| 817 |
``author_name=None, author_link=None, subtitle=None, categories=None,`` |
|---|
| 818 |
``feed_url=None)`` |
|---|
| 819 |
|
|---|
| 820 |
Initializes the feed with the given metadata, which applies to the entire feed |
|---|
| 821 |
(i.e., not just to a specific item in the feed). |
|---|
| 822 |
|
|---|
| 823 |
All parameters, if given, should be Unicode objects, except ``categories``, |
|---|
| 824 |
which should be a sequence of Unicode objects. |
|---|
| 825 |
|
|---|
| 826 |
``add_item(title, link, description, author_email=None, author_name=None,`` |
|---|
| 827 |
``pubdate=None, comments=None, unique_id=None, enclosure=None, categories=())`` |
|---|
| 828 |
|
|---|
| 829 |
Add an item to the feed with the given parameters. All parameters, if given, |
|---|
| 830 |
should be Unicode objects, except: |
|---|
| 831 |
|
|---|
| 832 |
* ``pubdate`` should be a `Python datetime object`_. |
|---|
| 833 |
* ``enclosure`` should be an instance of ``feedgenerator.Enclosure``. |
|---|
| 834 |
* ``categories`` should be a sequence of Unicode objects. |
|---|
| 835 |
|
|---|
| 836 |
``write(outfile, encoding)`` |
|---|
| 837 |
|
|---|
| 838 |
Outputs the feed in the given encoding to outfile, which is a file-like object. |
|---|
| 839 |
|
|---|
| 840 |
``writeString(encoding)`` |
|---|
| 841 |
|
|---|
| 842 |
Returns the feed as a string in the given encoding. |
|---|
| 843 |
|
|---|
| 844 |
Example usage |
|---|
| 845 |
------------- |
|---|
| 846 |
|
|---|
| 847 |
This example creates an Atom 1.0 feed and prints it to standard output:: |
|---|
| 848 |
|
|---|
| 849 |
>>> from django.utils import feedgenerator |
|---|
| 850 |
>>> f = feedgenerator.Atom1Feed( |
|---|
| 851 |
... title=u"My Weblog", |
|---|
| 852 |
... link=u"http://www.example.com/", |
|---|
| 853 |
... description=u"In which I write about what I ate today.", |
|---|
| 854 |
... language=u"en") |
|---|
| 855 |
>>> f.add_item(title=u"Hot dog today", |
|---|
| 856 |
... link=u"http://www.example.com/entries/1/", |
|---|
| 857 |
... description=u"<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>") |
|---|
| 858 |
>>> print f.writeString('utf8') |
|---|
| 859 |
<?xml version="1.0" encoding="utf8"?> |
|---|
| 860 |
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title>My Weblog</title> |
|---|
| 861 |
<link href="http://www.example.com/"></link><id>http://www.example.com/</id> |
|---|
| 862 |
<updated>Sat, 12 Nov 2005 00:28:43 -0000</updated><entry><title>Hot dog today</title> |
|---|
| 863 |
<link>http://www.example.com/entries/1/</link><id>tag:www.example.com/entries/1/</id> |
|---|
| 864 |
<summary type="html"><p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p></summary> |
|---|
| 865 |
</entry></feed> |
|---|
| 866 |
|
|---|
| 867 |
.. _django/utils/feedgenerator.py: http://code.djangoproject.com/browser/django/trunk/django/utils/feedgenerator.py |
|---|
| 868 |
.. _Python datetime object: http://www.python.org/doc/current/lib/module-datetime.html |
|---|