Este puede que sea uno de mis posts más originales; quiero decir, uno donde prácticamente todo el argumento es creación mía. Un fenómeno que está ocurriendo en Internet es que muchos sitios se hacen eco de noticias conocidas, realmente aportando poco o nada de contenido nuevo sobre la noticia. O compilan varias noticias relacionadas y con eso hacen una historia, pero aparte de la redacción, no hay nada nuevo (a mí también me pasó).
Pero no es el tema de este post. Ahora trataré de introducir un concepto importante (IMHO (http://en NULL.wikipedia NULL.org/wiki/IMHO#IM)) para la programación y cómo trato de resolver un problema (de programación, claro) utilizando este concepto.
Django
Django (http://www NULL.djangoproject NULL.com/) es un framework para programación web (http://en NULL.wikipedia NULL.org/wiki/Web_framework), uno de los mejores hoy en día. Los frameworks (http://en NULL.wikipedia NULL.org/wiki/Software_framework) son una parte importante del desarrollo de muchos sistemas informáticos, pues ayudan en tareas básicas o repetitivas, y crean un ambiente uniforme para el desarrollo abstrayéndose de las peculiaridades de otros sistemas; permitiendo al programador crear y concentrarse en la lógica de la aplicación (a.k.a. “the fun part”).
Este framework está basado en el lenguaje de programación Python (http://en NULL.wikipedia NULL.org/wiki/Python_(programming_language)), y promueve el rápido desarrollo de aplicaciones y diseño limpio y pragmático (o eso entendí). También se enfoca en la mayor automatización posible y en el principio DRY (http://c2 NULL.com/cgi/wiki?DontRepeatYourself).
DRY
No tiene nada que ver con ninguna secadora de ropa. Es un principio a tener en cuenta a la hora de diseñar un sistema (muchos de esos principios tienen nombrecitos graciosos (http://en NULL.wikipedia NULL.org/wiki/List_of_software_development_philosophies)).
En inglés (http://c2 NULL.com/cgi/wiki?DontRepeatYourself):
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
Básicamente, todo código o información dentro de un sistema debe estar definido una y sólo una vez en todo el sistema. La duplicación es mala y conlleva a pesadillas de mantenimiento, dando lugar a posibles inconsistencias. Cuando un sistema es completamente DRY, la automatizaión es tarea fácil, pues sólo es necesario definir una vez las transformaciones y el código sólo se reusa.
Por ejemplo, en un sistema que almacena información de usuarios, el nombre de cada uno de estos, está únicamente en una fila de una de las tablas de la base de datos. Tener la en dos o más lugares implicaría posibles inconsistencias: cuando se modifica el de una tabla, es necesario también reflejar el nuevo valor en la otra, pero y si falla algo en el medio?
Uno de los ejemplos claves de DRY en Django, es cómo obtener las URLs de una funcionalidad específica. Por ejemplo, el patrón de la URL para editar la información de un usuario, podría ser así (omitiendo el dominio):
r'^profiles/(?P<user_id>\d+)/edit/$'
Lo que está entre paréntesis es el parámetro que indica el identificador del usuario. Una URL de estas así:
'/profiles/19/edit/'
La cual llevaría a la página de edición correspondiente al usuario con el identificador «19». El problema es, ¿cómo incluir esta dirección en la página generada dinámicamente? El programador ingenuo (como todos al principio, incluyéndome) piensa en esto:
<a href="/profiles/{{ user.id }}/edit/">Editar perfil</a>
Y los problemas comienzan cuando la URL debe cambiar (por ejemplo, en vez de “profiles”, debe ser “users”). Es necesario cambiar la URL en la comfiguració Y en la plantilla. Por lo tanto, el patrón de la URL está duplicado.
En este caso, Django proporciona una función para obtener las URLs de cualquier página del sistema mirando sólo la configuración. Es posible incluso asignarle nombres fáciles de recordar y luego obtenerlas por estos nombres. Por ejemplo, en la plantilla HTML quedaría así:
<a href="{% url user_profile_edit user.id %}"<Editar perfil</a>
A partir de entonces, la URL podrá cambiar todo lo que quiera, pero mientras se use la etiqueta {% url %}, siempre se mostrará la dirección correcta.
El problema
El framework de Django para la generación de feeds (http://en NULL.wikipedia NULL.org/wiki/Web_feed) es sencillo, fácilmente extendible y completo; con entender unos conceptos y reusar un poco de código de otras partes de la aplicación, en un dos por tres estoy generando feeds para cualquier parte del sistema. Incluso facilita enormemente la generación de feeds que dependen de varios parámetros, como categorías o etiquetas.
Que le estaba agregando feeds RSS (http://en NULL.wikipedia NULL.org/wiki/RSS) a un proyecto que tengo en Django, cuando me doy cuenta de que la etiqueta HTML que referencia los los feeds, tiene más que sólo la dirección:
<link rel="alternate" type="application/rss+xml" title="Latest News" href="/feeds/latest/" />
Para el atributo “href” se puede utlizar la misma etiqueta {% url %}, pero no basta. De dónde obtengo el valor para el atributo “type”. Y más importante, el atributo “title”, que describe el nombre del feed dentro de la página actual.
Por ejemplo, sigamos con el título. Si es un feed único para todo el sitio, no hay mucho problema, poner un título genérico no dará muchos problemas. Como mostré antes, Firefox muestra el símbolo de feeds en la barra de direcciones si hay uno o más feeds disponibles en la página actual. Si hay más de uno, al hacer clic en el ícono muestra un menú con los feeds disponibles, usando el atributo “title” de cada uno como texto de las opciones del menú.
Por ejemplo, estando en una página con el contenido filtrado por una categoría, y quiero generar un feed que muestre las últimas noticias dentro de esta categoría, es normal que el título del feed incluya el nombre de la categoría; algo como “Latest News in Computers”, donde «Computers» es el nombre de la categoría. Firefox mostraría dos entradas en el menú de feeds “Latest News” (últimas de todo el sitio) y ”Latest News in Computers” (últimas noticias bajo la categoría «Computers»).
Claro, puedo generar el valor de “title” en la misma plantilla, pero es ahí donde estoy violando el principio DRY. El título del feed se generaría en dos lados, en el código de definición del feed y en la plantilla HTML.
Lo mismo pasa con el atributo “type”. Si se define si se usa Atom (http://en NULL.wikipedia NULL.org/wiki/Atom_(standard)) o RSS, se define en el código, pero también es necesario que se refleje el tipo correcto en la etiqueta “link”.
Mi solución
Aún cuando no estoy completamente feliz con ella, es lo mejor que he podido concebir. Este problema también lo planteé (http://stackoverflow NULL.com/questions/2784659/django-dry-feeds) en Stack Overflow (http://stackoverflow NULL.com/), pero sin respuestas convincentes. Y lo que más me convenció fue crear una etiqueta nueva. También consideré usar un procesador de contexto, pero ese camino tiene muchos inconvenientes.
El sistema de plantillas de Django permite que el programador cree nuevas plantillas y se las incluya al sistema, haciéndolas disponibles a toda la aplicación. Y como básicamente pretendo obtener la URL de un feed más algunos atributos, utilicé la etiqueta antes mencionada {% url %} y adaptarla a mis necesidades.
Aparte del nombre (que tampoco estoy contento con él, pero no se me ocurre otro), la etiqueta {% feed_info %} creada por mi funciona prácticamente igual que su hermana {% url %}. Lo que cambia es el valor de retorno que en este caso genera la etiqueta HTML “link” completa.
El código diferente es (agregado más o menos aquí (http://code NULL.djangoproject NULL.com/browser/django/trunk/django/template/defaulttags NULL.py#L382)):
if 'request' in context:
request = context['request']
else:
request = None
feed_instance, feed_args, feed_kwargs = resolve(url)
if not isinstance(feed_instance, Feed):
raise NoReverseMatch, \
'feed_info can only reverse class-based feeds'
feed_obj = feed_instance.get_object(request, *feed_args, **feed_kwargs)
feed_data = {
'url': url,
'obj': feed_instance,
'args': feed_args,
'kwargs': feed_kwargs,
#'title': html_escape(feed_instance.__get_dynamic_attr('title', obj)),
'title': html_escape(
feed_instance._Feed__get_dynamic_attr('title', feed_obj)
),
'type': feed_instance.feed_type.mime_type,
}
if self.asvar:
context[self.asvar] = feed_data
return ''
else:
return mark_safe(
'<link rel="alternate" type="%(type)s" title="%(title)s" href="%(url)s" />' \
% feed_data
)
Empezamos. Los métodos de Feed que obtienen parámetros también reciben como primer parámetro un objeto request, para dar mayor flexibilidad. Pero aquí aparece un inconveniente, para que sea realmente genérico el contexto de la plantilla debe contener una variable con el objeto request. Esta variable solo está presente si el procesador de contexto (http://docs NULL.djangoproject NULL.com/en/dev/ref/templates/api/#django-core-context-processors-request) está presente en la configuración (http://docs NULL.djangoproject NULL.com/en/dev/ref/settings/#template-context-processors). En caso contrario, si nuestro feed personalizado utiliza datos del request para generarse, este puede no funcionar bien.
Seguimos. El código de la etiqueta {% url %} ya hizo todo el trabajo de averiguar la URL del feed, con parámetros y todo, como debe ser. Hasta ahora, tengo sólo la dirección del feed (el atributo “href” de “link”). Para obtener el valor de los otros dos atributos, necesito tener la instancia de Feed a la cual preguntarle por sus propiedades.
Ahora tengo una ventaja. En Django 1.2 (http://code NULL.djangoproject NULL.com/wiki/Version1 NULL.2Features), los feeds son instancias de subclases de Feed, pero que permiten ser llamadas como funciones, por lo que son usadas también como vistas. En versiones anteriores, me imagino que hubiera sido un poco más difícil.
La ventaja la aprovecho usando resolve(). Esta función, dado una URL concreta, devuelve la vista, y los argumentos que se le pasan a dicha vista. Y la vista retornada, es la instancia de la clase personalizada de Feed. También compruebo si realmente es un Feed de nuevo tipo.
Luego creo un diccionario con toda la información necesaria del feed (URL, título, tipo, la instancia misma y los argumentos que se le pasaron). Por si las moscas…
Finalizando. Si la etiqueta se invocó utilizando as «variable», el diccionario antes creado se incluye en el contexto de la plantilla con el nombre especificado y no se devuelve nada. Por el contrario, si no se especifica en qué variable guardarse esta información, se genera la etiqueta “link” con los atributos “title”, “type” y “href”.
Pero no todo me salió tan lindo. En el código de creación del diccionario con la información del feed, hay una línea comentada. Así es como se supone que funcione, teniendo en cuenta la definición de la clase Feed (http://code NULL.djangoproject NULL.com/browser/django/trunk/django/contrib/syndication/views NULL.py#L24). Ese método “mágico”, __get_dynamic_attr() (http://code NULL.djangoproject NULL.com/browser/django/trunk/django/contrib/syndication/views NULL.py#L52), no aparece en la instancia del feed. En cambio, aparece con el nombre _Feed__get_dynamic_attr. No entiendo eso, pero funciona. Lo que preocupa es que esta forma de llamarse el método cambie… a veces estas magias de Python me confunden…