lunes, 2 de marzo de 2009

No me gustan los templates de Django ...

Había una vez un programador solitario que comentó que no le gustaban los templates de Django. ¡Semejante herejía! ¿Cómo se le iba a ocurrir decir eso del framework que el propio Guido van Rossum ha sugerido para ser desplegado en Google App Engine? Como todos se quedaron impávidos, y como ese programador soy yo ... aquí les van los detalles. ¿Cuáles son mis argumentos? ¿Cuál es el mejor sistema de templates para Python? ¿Realmente alguno sobresale? Todo esto y muchas cosas más las tendrá acá ... continúe leyendo hasta el final ... y si se motiva, ¡puede dejar también su comentario! ;) ... pero primero es lo primero. Todo comenzó con un tablero de ajedrez ...

Antecedentes

Cuando intenté aprender cómo utilizar Django me dije: comenzaré por hacer algo bien sencillo, pero no muy tradicional, para ir familiarizándome con los templates. Fue entonces cuando comenzó mi Odisea: se me ocurrió mostrar un tablero de ajedrez utilizando tablas HTML. En este momento tenía el antecedente de JSP después de terminar un curso de J2EE ofrecido por profesores del NIIT. Ya había logrado comprender su estilo hasta el punto de darme cuenta de que podía ser dañino si se mezclaban los detalles de la interfaz con otra lógica más propia de la aplicación. Además esto no mejora mucho tampoco utilizando JavaBeans. Pero realmente en ese tiempo (ahora realmente no sé ;) me gustaba mucho la idea de dejar abierto un bloque (e.g. un for) en el template, intercalar el código HTML que se iba a ir repitiendo y cerrar el ciclo más adelante. Antes de conocer incluso a Django, cuando comencé a explorar el desarrollo de aplicaciones web con Python esperé que toda la naturalidad del lenguaje redundaría en templates dignos de admiración. Por tanto comencé por echarle una ojeada a mod_python. Me sorprendí al ver sus similitudes con JSP, y por tanto era consciente también de sus limitaciones. Pero bueno, al grano, supongamos que se tiene el ancho de las celdas (celldim), dos listas que contienen los identificadores de columnas (cols) y filas (rows) y una variable que indica si el jugador lleva las piezas blancas o negras (home_player). Después de todo esto mi famoso tablero de ajedrez sería tan sencillo como:
<%
celldim = 20
rows = "abcdefgh"
cols = range(1,9)
home_player = True
%>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<style type="text/css">
.white_sq{
background-color: white;
}
.black_sq{
background-color: black;
}
</style>
</head>
<body>
<table>
<tr height="<%= celldim %>">
<td width="<%= celldim %>"></td>
<%
for ci, c in enumerate(home_player and cols or reversed(cols)):
%>
<td width="<%= celldim %>">
<small><%= c %></small>
</td>
<%
%>
</tr>
<%
for ri, r in enumerate(home_player and reversed(rows) or rows):
# Rows in board ...
%>
<tr height="<%= celldim %>">
<td width="<%= celldim %>">
<small><%= r %></small>
</td>
<%
for ci, c in enumerate(home_player and cols or reversed(cols)):
# Cols in board ...
%>
<td class="<%= (ci + ri) % 2 == 0 and 'white_sq' or 'black_sq' %> "
width="<%= celldim %>"></td>
<%

%>
<td width="<%= celldim %>">
<small><%= r %></small>
</td>
</tr>
<%
%>
<tr height="<%= celldim %>">
<td width="<%= celldim %>"></td>
<%
for ci, c in enumerate(home_player and cols or reversed(cols)):
%>
<td width="<%= celldim %>">
<small><%= c %></small>
</td>
<%
%>
</tr>
</table>
</body>
</html>

Con Django ... ¡¡¡me duele la cabeza!!!

De pronto traté de reproducir la misma idea utilizando los templates de [www.djangoproject.com/ Django]. Atraído por los comentarios que me adelantaban sus virtudes (entre ellas su separación bien establecida entre la capa de presentación y la lógica de la aplicación, sumándole a esto su enfoque para persistencia) de datos, no me resultó nada raro pensar que este ejemplo tan sencillo iba a ser algo muy fácil de hacer ... sin embargo, como pueden ver, el resultado (hasta ahora ;) es realmente decepcionante.
<html>
<head>
<style type="text/css">
.white_sq{
background-color: white;
}
.black_sq{
background-color: black;
}
</style>
</head>
<body>
<table>
<tr>
<td width="{{celldim}}"></td>
{% for c in cols %}
<td width="{{celldim}}">
<small>
  {% if home_player %}
    {{c}}
  {% else %}
    {{cols|slice:forloop.revcounter.__str__|slice:"-1::-1"|first}}
  {% endif %}
</small>
</td>
{% endfor %}
<td width="{{celldim}}"></td>
</tr>
{% for r in rows reversed %}
<tr height="{{celldim}}">
<td width="{{celldim}}">
<small>
{% if home_player %}
  {{r}}
{% else %}
  {{rows|slice:"-1::-1"|slice:forloop.revcounter.__str__|slice:"-1::-1"|first}}
{% endif %}
</small>
</td>
{% for c in cols %}
<td class={% if forloop.counter|add:forloop.parentloop.counter|divisibleby:"2" %}
          "white_sq"
        {% else %}
          "black_sq"
        {% endif %}
  width="{{celldim}}"></td>
{% endfor %}
<td width="{{celldim}}">
<small>
{% if home_player %}
  {{r}}
{% else %}
  {{rows|slice:"-1::-1"|slice:forloop.revcounter.__str__|slice:"-1::-1"|first}}
{% endif %}
</small>
</td>
</tr>
{% endfor %}
<tr>
<td width="{{celldim}}"></td>
{% for c in cols %}
<td width="{{celldim}}">
<small>
  {% if home_player %}
    {{c}}
  {% else %}
    {{cols|slice:forloop.revcounter.__str__|slice:"-1::-1"|first}}
  {% endif %}
</small>
</td>
{% endfor %}
<td width="{{celldim}}"></td>
</tr>
</table>
</body>
</html>
El código es altamente ilegible, no tiene nada de naturalidad, su ejecución es ineficiente debido a que hay que procesar varios filtros (y evidentemente eso tarda más que una evaluación directa contra un espacio de nombres global ;). Además, para lograr hacerlo hay que pasar por un proceso de aprendizaje nada despreciable. ¡Qué desastre! Mi impresión es que los templates de [www.djangoproject.com/ Django] se han tornado tan restrictivos para separar los detalles de la interfaz de lo demás, que han convertido en un infierno hacer rápida y naturalmente tareas extremadamente sencillas que no tienen nada que ver con la lógica de la aplicación.

Por suerte llegó Genshi ... y mandó a parar

Más tarde entré en contacto con Genshi durante mi aventurero desarrollo de plugins para Trac. En este caso se alcanza un mejor equilibrio entre la programación de la interfaz y su separación ¡Qué alegría! Para empezar mi tablero de ajedrez quedaría apróximadamente así ...
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/">
<head>
<style type="text/css">
.white_sq{
background-color: white;
}
.black_sq{
background-color: black;
}
</style>
</head>
<body>

<py:def function="label_row()">
<tr height="$celldim">
<td width="$celldim"></td>
<td py:for="ci, c in enumerate(home_player and cols or reversed(cols))"
  width="$celldim">
<small>$c</small>
</td>
<td width="$celldim"></td>
</tr>
</py:def>
<py:def function="label_cell(r)">
<td width="$celldim">
<small>$r</small>
</td>
</py:def>

<table>
${label_row()}
<tr py:for="ri, r in enumerate(home_player and reversed(rows) or rows)"
height="$celldim">
${label_cell(r)}
<td py:for="ci, c in enumerate(home_player and cols or reversed(cols))"
class="${(ci + ri) % 2 == 0 and 'white_sq' or 'black_sq'} "
width="$celldim"></td>
${label_cell(r)}
</tr>
${label_row()}
</table>
</body>
</html>
... y además fíjese en la reutilización que permite el elemento py:def para crear estructuras semejantes a funciones (digamos def) que se expresan solamente en términos de elementos XML / HTML. Además Genshi está preparado para hacer streamming, lo que contribuye a la eficiencia para expandir los templates, y para filtrarlos (e.g. para lograr con relativa fácilidad, la preparación de sitios web completos en diferentes idiomas).

Pero cuidado con Cheetah …

Después de haber escuchado a Alex Martelli en su presentación de Python durante Google I/O 2008 me interesé por saber un poco más de Cheetah. Aunque mi corta experiencia no me permitiría hacer un análisis más profundo ahora (... quizás luego ;), sí les puedo asegurar que mi tablero sería especificado con un template como el siguiente:
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<style type="text/css">
.white_sq{
background-color: white;
}
.black_sq{
background-color: black;
}
</style>
</head>
#def label_row()
<tr height="$celldim">
<td width="$celldim"></td>
#for $ci, $c in enumerate($home_player and $cols or reversed($cols))
<td width="$celldim">
<small>$c</small>
</td>
#end for
<td width="$celldim"></td>
</tr>
#end def
#def label_cell($r)
<td width="$celldim">
<small>$r</small>
</td>
#end def
<body>
<table>
$label_row()
#for $ri, $r in enumerate($home_player and reversed($rows) or $rows)
<tr height="$celldim">
$label_cell($r)
#for $ci, $c in enumerate($home_player and $cols or reversed($cols))
<td class="${(ci + ri) % 2 == 0 and 'white_sq' or 'black_sq'} "
width="$celldim"></td>
#end for
$label_cell($r)
</tr>
#end for
$label_row()
</table>
</body>
</html>
... es decir, se repite la reutilización de código con la directiva #def para crear estructuras semejantes a funciones, parecido al caso de Genshi. Además Cheetah compila los templates a código Python, lo que le aporta potencialidades increíbles al framework (... de las que quizás podremos hablar más adelante ...). Solo les adelanto lo siguiente, Cheetah ha sido utilizado para generar HTML, SGML, XML, SQL, Postscript, e-mail y LaTeX. En la práctica es capaz de hacerlo con cualquier otro formato de texto y, en otras aplicaciones menos ortodoxas, se ha generado código para lenguajes como Python, Java y PHP. Además Cheetah permite utilizar otras sintaxis, lo que implica que se pueda, por ejemplo, intercalar código PSP en templates de Cheetah.

Conclusiones

Sinceramente quisiera pensar que no comprendo los templates de [www.djangoproject.com/ Django] tan bien como para obtener lo que realmente quiero de una forma más natural y sencilla. Pero luego he descubierto que parece ser que no soy el único. Les dejo con los principios de diseño detrás de Cheetah, saquen Ustedes sus propias conclusiones :
  • Python para la lógica de la aplicación, y Cheetah para la capa de presentación. Cheetah está diseñado para complementar a Python, no para remplazarlo.
  • La sintaxis debe ser fácil de aprender por personas que no programen.
  • La reutilización de código debe estar disponible de forma sencilla, al proveer una interfaz orientada a objetos que facilite el acceso a los datos en el template ya sea desde Python o desde otros templates de Cheetah.
  • Los objetos, funciones, y otras estructuras de datos presentes en Python deben ser accesibles en los templates de Cheetah.
  • Cheetah debe proveer directivas de control de flujo y manipulación de errores. La lógica que pertenece a la capa de presentación no debe ser relegada a la capa de la aplicación, simplemente porque es compleja.
  • El contenido, el diseño gráfico y el código de la aplicación deben ser separados, y a la vez integrados, de manera fácil.
  • En particular debe resultar intuitivo:
    • para los programadores, poder crear componentes reutilizables y funciones accesibles y comprensibles por los diseñadores.
    • para los diseñadores, el poder marcar los espacios dónde se ubicarán los diferentes contenidos y componentes dinámicos en los templates.
    • para los diseñadores también, el poder escribir código simple para poder expresar aspectos que en el diseño resultan ser repetitivos o que cambian con facilidad.
    • para los diseñadores también, el poder reutilizar y extender plantillas existentes y minimizar así la duplicación del esfuerzo y las líneas de código.
    • y, claro, para los autores de los contenidos, utilizar las plantillas creadas por los diseñadores.
Sin embargo ... al margen de todo este debate, para aplicaciones más serias mantengo mi posición de liberarme lo más posible de la tiranía de los frameworks. Si Ud tiene otra opinión no hay nada para mí más valioso que conocer sus comentarios ... y, simelo pide, seguiremos explorando este hermoso universo de la programación web en Python.

No hay comentarios:

Publicar un comentario