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á enfeeds/latest/
. - El feed representado por
LatestEntriesByCategory
residirá enfeeds/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
, ydescription
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 objetosNewsItem
usando la API de base de datos de Django, no es un requerimiento queitems()
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 enitems()
).site
: Un objetodjango.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 0613http://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/
:
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étodoget_object()
de la claseFeed
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']
.get_object()
es el responsable de obtener el departamento requerido, a partir delbits
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óndjango.core.exceptions.ObjectDoesNotExist
si recibe parámetros inválidos. No haytry
/except
abarcando la llamada aBeat.objects.get()
porque no es necesario. Esa función, ante una falla lanza la excepciónBeat.DoesNotExist
, yBeat.DoesNotExist
es una subclase deObjectDoesNotExist
. Lanzar la excepciónObjectDoesNotExist
enget_object()
le dice a Django que produzca un error 404 para el requerimiento en curso.- Para generar los campos
<title>
,<link>
, y<description>
del feed, Django usa los métodostitle()
,link()
, ydescription()
. En el ejemplo anterior, se utilizaron atributos simples de clase string, pero este ejemplo muestra que estos pueden ser strings o métodos. Por cadatitle
,link
, ydescription
, Django sigue este algoritmo:- Trata de llamar al método, pasando el argumento
obj
, dondeobj
es el objeto retornado porget_object()
. - Si eso falla, trata de llamar al método sin argumentos.
- Si eso falla, usa los atributos de clase.
- Trata de llamar al método, pasando el argumento
- Finalmente, nota que
items()
en el ejemplo también toma como argumento aobj
. El algoritmo paraitems
es el mismo que se describe en el paso anterior — primero pruebaitems(obj)
, despuésitems()
, y finalmente un atributo de claseitems
(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}),
# ...
)