El libro de Django 1.0

11.5. El Framework de Feeds de Sindicación

Django incluye un framework para la generación y sindicación de feeds de alto nivel que permite crear feeds RSS y Atom de manera sencilla.

Nota RSS y Atom son formatos basados en XML que se puede utilizar para actualizar automáticamente los "feeds" con el contenido de tu sitio. Leer más sobre RSS o sobre Atom.

Para crear cualquier feed de sindicación, todo lo que debes hacer es escribir una corta clase Python. Puedes crear tantos feeds como desees.

El framework de generación de feeds de alto nivel es una vista enganchada a /feeds/ por convención. Django usa el final de la URL (todo lo que este después de /feeds/) para determinar qué feed retornar.

Para crear un feed, necesitas escribir una clase Feed y hacer referencia a la misma en tu URLconf (ver los Capítulos 3 y 8 para más información sobre URLconfs).

11.5.1. Inicialización

Para activar los feeds de sindicación en tu sitio Django, agrega lo siguiente en tu URLconf:

(r'^feeds/(?P<url>.*)/$',
 'django.contrib.syndication.views.feed',
 {'feed_dict': feeds}
),

Esa línea le indica a Django que use el framework RSS para captar las URLs que comienzan con "feeds/". (Puedes cambiar "feeds/" por algo que se adapte a tus necesidades).

Esta línea de URLconf tiene un argumento extra: {'feed_dict': feeds}. Usa este argumento extra para pasar al framework de feeds de sindicación los feeds que deben ser publicados en dicha URL.

Específicamente, feed_dict debe ser un diccionario que mapee el slug (etiqueta corta de URL) de un feed a la clase Feed. Puedes definir el feed_dict en el mismo URLconf. Este es un ejemplo completo de 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}),
    # ...
)

El ejemplo anterior registra dos feeds:

  • El feed representado por LatestEntries residirá en feeds/latest/.
  • El feed representado por LatestEntriesByCategory residirá en feeds/categories/.

Una vez que este configurado, necesitas definir la propia clase Feed.

Una clase Feed es una simple clase Python que representa un feed de sindicación. Un feed puede ser simple (p. ej. "noticias del sitio", o una lista de las últimas entradas del blog) o más complejo (p. ej. mostrar todas las entradas de un blog en una categoría en particular, donde la categoría es variable).

La clase Feed debe ser una subclase de django.contrib.syndication.feeds.Feed. Esta puede residir en cualquier parte del árbol de código.

11.5.2. Un Feed simple

Este ejemplo simple, tomado de http://chicagocrime.org, describe un feed que muestra los últimos cinco items agregados:

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]

Las cosas importantes a tener en cuenta son:

  • La clase es subclase de django.contrib.syndication.feeds.Feed.
  • title, link, y description corresponden a los elementos RSS estándar <title>, <link>, y <description> respectivamente.
  • items() es simplemente un método que retorna una lista de objetos que deben incluirse en el feed como elementos <item>. Aunque este ejemplo retorna objetos NewsItem usando la API de base de datos de Django, no es un requerimiento que items() deba retornar instancias de modelos.
Obtienes unos pocos bits de funcionalidad "gratis" usando los modelos de
Django, pero `items()` puede retornar cualquier tipo de objeto que
desees.

Hay solamente un paso más. En un feed RSS, cada <item> posee <title>, <link>, y <description>. Necesitamos decirle al framework qué datos debe poner en cada uno de los elementos.

Para especificar el contenido de <title> y <description>, crea plantillas Django (ver Capítulo 4) llamadas feeds/latest_title.html y feeds/latest_description.html, donde latest es el slug especificado en URLconf para el feed dado. Notar que la extensión .html es requerida.

El sistema RSS renderiza dicha plantilla por cada ítem, pasándole dos variables de contexto para plantillas:

  • obj: El objeto actual (uno de los tantos que retorna en items()).
  • site: Un objeto django.models.core.sites.Site representa el sitio actual. Esto es útil para {{ site.domain }} o {{ site.name }}.

Si no creas una plantilla para el título o la descripción, el framework utilizará la plantilla por omisión "{{ obj }}" — exacto, la cadena normal de representación del objeto.

También puedes cambiar los nombres de estas plantillas especificando title_template y description_template como atributos de tu clase Feed.

Para especificar el contenido de <link>, hay dos opciones. Por cada ítem en items(), Django primero tratará de ejecutar el método get_absolute_url() en dicho objeto. Si dicho método no existe, entonces trata de llamar al método item_link() en la clase Feed, pasándole un único parámetro, item, que es el objeto en sí mismo.

Ambos get_absolute_url() y item_link() deben retornar la URL del ítem como una cadena normal de Python.

Para el ejemplo anterior LatestEntries, podemos usar plantillas de feed muy simples. latest_title.html contiene:

{{ obj.title }}

y latest_description.html contiene:

{{ obj.description }}

Es casi demasiado fácil.

11.5.3. Un Feed más complejo

El framework también permite la creación de feeds más complejos mediante el uso de parámetros.

Por ejemplo, http://chicagocrime.org ofrece un feed RSS de los crímenes recientes de cada departamento de policía en Chicago. Sería tonto crear una clase Feed separada por cada departamento; esto puede violar el principio "No te repitas" (DRY, por "Do not repeat yourself") y crearía acoplamiento entre los datos y la lógica de programación.

En su lugar, el framework de feeds de sindicación te permite crear feeds genéricos que retornan items basados en la información en la URL del feed.

En chicagocrime.org, los feed por departamento de policía son accesibles mediante URLs como estas:

  • http://www.chicagocrime.org/rss/beats/0613/: Retorna los crímenes más recientes para el departamento 0613
  • http://www.chicagocrime.org/rss/beats/1424/: Retorna los crímenes más recientes para el departamento 1424

El slug aquí es "beats". El framework de sindicación ve las partes extra en la URL tras el slug — 0613 y 1424 — y te provee un hook para que le indiques qué significa cada uno de esas partes y cómo influyen en los items que serán publicados en el feed.

Un ejemplo aclarará esto. Este es el código para los feeds por departamento:

from django.core.exceptions import ObjectDoesNotExist

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):
        return obj.get_absolute_url()

    def description(self, obj):
        return "Crimes recently reported in police beat %s" % obj.beat

    def items(self, obj):
        crimes =  Crime.objects.filter(beat__id__exact=obj.id)
        return crimes.order_by('-crime_date')[:30]

Aquí tenemos el algoritmo básico del framework RSS, asumiendo esa clase y un requerimiento a la URL /rss/beats/0613/:

  1. El framework toma la URL /rss/beats/0613/ y nota que la URL contiene una parte extra tras el slug. Separa esa cadena remanente por el carácter "/" y llama al método get_object() de la clase Feed pasándole los trozos (bits) resultantes.

    En este caso, los trozos "son" ['0613']. Para un requerimiento a /rss/beats/0613/foo/bar/, serán ['0613', 'foo', 'bar'].

  2. get_object() es el responsable de obtener el departamento requerido, a partir del bits dado.

    En este caso, usa la API de base de datos de Django para obtener el departamento. Notar que get_object() debe capturar la excepción django.core.exceptions.ObjectDoesNotExist si recibe parámetros inválidos. No hay try/except abarcando la llamada a Beat.objects.get() porque no es necesario. Esa función, ante una falla lanza la excepción Beat.DoesNotExist, y Beat.DoesNotExist es una subclase de ObjectDoesNotExist. Lanzar la excepción ObjectDoesNotExist en get_object() le dice a Django que produzca un error 404 para el requerimiento en curso.

  3. Para generar los campos <title>, <link>, y <description> del feed, Django usa los métodos title(), link(), y description(). En el ejemplo anterior, se utilizaron atributos simples de clase string, pero este ejemplo muestra que estos pueden ser strings o métodos. Por cada title, link, y description, Django sigue este algoritmo:
    1. Trata de llamar al método, pasando el argumento obj, donde obj es el objeto retornado por get_object().
    2. Si eso falla, trata de llamar al método sin argumentos.
    3. Si eso falla, usa los atributos de clase.
  4. Finalmente, nota que items() en el ejemplo también toma como argumento a obj. El algoritmo para items es el mismo que se describe en el paso anterior — primero prueba items(obj), después items(), y finalmente un atributo de clase items (que debe ser una lista).

La documentación completa de todos los métodos y atributos de las clases Feed siempre esta disponible en la documentación oficial de Django.

11.5.4. Especificar el tipo de Feed

Por omisión, el framework de feeds de sindicación produce RSS 2.0. Para cambiar eso, agrega un atributo feed_type a tu clase Feed:

from django.utils.feedgenerator import Atom1Feed

class MyFeed(Feed):
    feed_type = Atom1Feed

Nota que asignas como valor de feed_type una clase, no una instancia. Los tipos de feeds disponibles actualmente se muestran en la siguiente tabla.

Clase Feed Formato
django.utils.feedgenerator.Rss201rev2Feed RSS 2.01 (por defecto)
django.utils.feedgenerator.RssUserland091Feed RSS 0.91
django.utils.feedgenerator.Atom1Feed Atom 1.0

11.5.5. Enclosures

Para especificar enclosures (p. ej. recursos multimedia asociados al ítem del feed tales como feeds de podcasts MP3), usa los ganchos item_enclosure_url, item_enclosure_length, y item_enclosure_mime_type, por ejemplo:

from myproject.models import Song

class MyFeedWithEnclosures(Feed):
    title = "Example feed with enclosures"
    link = "/feeds/example-with-enclosures/"

    def items(self):
        return Song.objects.all()[:30]

    def item_enclosure_url(self, item):
        return item.song_url

    def item_enclosure_length(self, item):
        return item.song_length

    item_enclosure_mime_type = "audio/mpeg"

Esto asume, por supuesto, que has creado un objeto Song con los campos song_url y song_length (p. ej. el tamaño en bytes).

11.5.6.  Idioma

Los Feeds creados por el framework de sindicación incluyen automáticamente la etiqueta <language> (RSS 2.0) o el atributo xml:lang apropiados (Atom). Esto viene directamente de tu variable de configuración LANGUAGE_CODE.

11.5.7.  URLs

El método/atributo link puede retornar tanto una URL absoluta (p. ej. "/blog/") como una URL con el nombre completo de dominio y protocolo (p. ej. "http://www.example.com/blog/"). Si link no retorna el dominio, el framework de sindicación insertará el dominio del sitio actual, acorde a la variable de configuración SITE_ID.

Los feeds Atom requieren un <link rel="self"> que define la ubicación actual del feed. El framework de sindicación completa esto automáticamente, usando el dominio del sitio actual acorde a la variable de configuración SITE_ID.

11.5.8. Publicar feeds Atom y RSS conjuntamente

Algunos desarrolladores prefieren ofrecer ambas versiones Atom y RSS de sus feeds. Esto es simple de hacer con Django: solamente crea una subclase de tu clase feed y asigna a feed_type un valor diferente. Luego actualiza tu URLconf para agregar una versión extra. Aquí un ejemplo completo:

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

Y este es el URLconf asociado:

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}),
    # ...
)