
Ahora que estoy trabajando con django estoy viendo lo potente que es, pero su sintaxis y forma de hacer las cosas no me acaba de convencer del todo comparandolo con web.py.
Una de las cosas que me gustan mucho de web.py es la forma de definir las vistas, que consiste en una clase donde defines el método GET y el método POST (no son obligatorios) y estos métodos son llamados según sea la petición http. Esto es muy util a la hora de hacer vistas de formularios, que si reciben la petición por GET muestran el formulario y si la reciben por POST hacen lo que sea con los datos, y en django suele haber un if request.method == 'POST' que guarrea la vista completamente.
Por eso hoy me he puesto y he hecho una pequeña prueba de concepto para poder permitir una definición similar en django, y así poder tener una mejor reestructuración del código de las vistas.
Lo he puesto en django snippets, pero también lo voy a poner aquí y lo comento:
from django.http import HttpResponse as response from django.http import HttpResponseNotAllowed class ViewClass: def __call__(self, request, *args, **kwargs): self.request = request methods = ['POST', 'GET'] self.methods = [method for method in dir(self)\ if callable(getattr(self, method)) and method in methods] if request.method in self.methods: view = getattr(self, request.method) return view(*args, **kwargs) else: return HttpResponseNotAllowed(self.methods) class IndexView(ViewClass): def GET(self): return response("all ok %s" % self.request.method) def POST(self): return response("all ok %s" % self.request.method) index = IndexView()
¿Qué es lo que he hecho? He definido mi vista IndexView como una clase, que hereda de "ViewClass" (ahí está la mágia), donde me he definido mis dos métodos, GET y POST, igual que si lo hubiera hecho con web.py, suponiendo que GET se llamará cuando la petición sea GET y POST cuando sea POST, y el request en lugar de recibirlo como parámetro es un atributo de la clase, así que accedo a él con self.request.
Luego defino la vista como index = IndexView(), una instancia de la clase IndexView.
ViewClass es la clase de la que heredamos y la que implementa el método __call__. En python, si un objeto tiene el método __call__ es callable, y se puede llamar igual que una funcion. Es más, en python, las funciones son objetos que tienen definido el método __call__. Por tanto, como django espera que la vista sea una función, no una clase, django va a llamar al método call y le va a pasar los argumentos de la vista.
En el método __call__ lo que se hace es mirar si el tipo de petición HTTP está implementado en la clase y es uno de los válidos (methods) y si existe en la clase un método con ese nombre, lo llama pasandole los argumentos y poniendo previamente self.request = request. En caso de que el método no esté implementado devuelve responsenotallowed.
Para terminar, quiero comentar que otra de las cosas que no me gusta de django es el sistema de templates, con tantas llaves y porcentages por ahí, me parece mucho más elegante templetor, pero también es verdad que los templates de django son muy potentes y se pueden hacer muchas cosas.

Aunque no escriba mucho en el blog el desarrollo de TBO sigue adelante. He hecho algunas cosillas interesantes en cuanto a la estructura del proyecto y a la "metodología" de desarrollo y en cuanto al código el proyecto empieza a tomar color y ya casi parece una aplicación :P

Esta es la pinta que tiene ahora mismo la aplicación. Ahora mismo se puede:
- Crear un nuevo cómic
- Añadir/borrar/moverse entre páginas
- Añadir/borrar/seleccionar/mover/escalar viñetas
Próximos pasos:
- Añadir una barra lateral para meter controles de las herramientas
- Implementar el guardado y apertura de ficheros .tbo (tengo que definir cómo serán los ficheros .tbo)
- Añadir el modo de edición de viñeta (aquí está to el meollo)
Cambios estructurales/desarrollo
Con respecto al desarrollo, he dejado de usar cmake para usar las autotools. Esto puede ser una decisión arbitraria, y en realidad lo es, pero es que la mayoría de los proyectos gnome/gtk utilizan este tipo de herramientas y para hacer más "compatible" TBO he decidido utilizar esto.
El cambio de cmake a autotools no ha sido traumático ni mucho menos, hay un montón de documentación por ahí y además me he basado en el código del proyecto epiphany.
Por otra parte, antes estaba usando bazaar (además del svn de rediris con bzr-svn) para gestionar las versiones de TBO, pero me ha dado por cambiarme a git, y ahora es lo que estoy usando (con git svn para el svn de rediris). Así que la evolución del proyecto se puede seguir los cambios desde aquí.
Por ahora no tengo mucho más que decir. Si todo va bien y consigo una versión funcional quiero intentar meter TBO en el proyecto gnome, guadalinex->ubuntu->debian y que sea algo más o menos utilizado.
Todas las sugerencias, parches y críticas serán bien recibidas.


En los aeropuertos se están poniendo ya muy cabrones, que te hagan pasar por un control, vale, por un detector de metales, vale, que te quiten las botellas de agua y los batidos de chocolate ... vale, que te cacheen .......... vale, que te pregunten de dónde vienes y a dónde vas y para qué ... .. .. .. .. .. vale, que te miren con un escaner que te desnuda ..... .. .. .... .. . vale, que te metan el dedo en el culo por si llevas una bomba. .. .. ... .. ..... . .. ... vale. ¿Hasta dónde vamos a llegar? Bueno, cada cual tendrá sus límites.

Ya estamos llegando al final del año 2009, y mi avatar está ya un poco anticuado, así que me he puesto a dibujar un nuevo avatar para el nuevo año y este ha sido el resultado:

Aquí está el video del proceso con musiquita y todo:
La canción es "Cerca de Shibuya" de "La casa azul".
Este dibujo está realizado en un "asus eee901" con Archlinux con el trackpad usando inkscape 0.46, grabado con gtk-recordmydesktop y editado con mencoder. El audio ha sido editado con audacity.

El otro día leí en reddit que emacs se pasaba a bazaar y en los comentarios leí que bazaar no es lo mejor del mundo. Yo ya lo sabía, y después de haber tratado durante algún tiempo con bazaar me decidí a cambiar todos mis repositorios a git.
¿Cómo hacer eso? pues fué más fácil de lo esperado, con el plugin de bazaar fastimport y el comando git import. Se instala fácilmente descargandolo "bzr branch lp:bzr-fastimport fastimport" y copiando el directorio a "~/.bazaar/plugins/". Ahora deberíamos tener accesible el comando bzr fast-export.
En mi servidor tengo todas mis ramas dentro de un directorio llamado branchs y quería crear un directorio llamado gits con los mismos proyectos pero portados. Para ello me he creado un script que se encarga de hacer todo lo necesario:
#!/bin/bash root=$PWD cd $root/branchs for i in * do newdir=$root/gits/$i.git mkdir $newdir cd $newdir git init --bare bzr fast-export $root/branchs/$i | git fast-import git update-server-info done
Esto exporta todos los repositorios de bazaar a git.
Y para mostrar todas las ramas de git que tengo me he instalado gitPHP, porque intenté configurar gitweb, pero con lighttpd no he podido hacer funcionar este porque es un script perl, he intentado seguir las instrucciones para configurar gitweb con lighttpd, pero no me muestra la página, me da el fichero para descargar.
Sobre la decisión de usar git en lugar de bazaar, es una decisión totalmente arbitraria, con poquísimo fundamento, bien podría haber sido mercurial o lo que sea, pero git está siendo utilizado por grandes proyectos como gnome o kde y se supone que es el más potente, así que por qué no.

Ahora que he decidido implementar TBO con C me han surgido problemas que no había tenido en cuenta, como por ejemplo la compilación. Con python no es necesario compilar nada, así que puedes escribir tu código modularizado sin ninguna precupación, pero cuando comienzas a escribir códgio en C y separas funcionalidad en diferentes ficheros comienzas a necesitar herramientas que faciliten la compilación, como son los makefiles, que compila solo las partes modificadas no teniendo así que compilar todo el proyecto siempre que se haga una modificación, sino que simplemente se compila lo modificado.
Los makefiles ofrecen una gran potencia, pero escribir un makefile completo con soporte multiplataforma es muy complejo, así que existen herramientas que te facilitan la vida como las autotools o cmake.
Para estas herramientas tú escribes un fichero donode especificas qué quieres compilar (e instalar con make install) y qué librerías necesitas, con número de versión incluso, y con un simple comando comprueban que todas las librerías necesarias están instaladas y generan un makefile para poder compilar e instalar.
La mayoría de los usuarios avanzados de linux habrá utilizado alguna vez las autotools para compilar, ejecutar un ./autogen.sh, un ./configure, etc. Yo me he decidido por utilizar cmake. Por qué cmake, pues porque me lo recomendó Alex (del proyecto robodo), es lo que usan en kde para compilar todo, me lo explicó por encima y me pasó un tutorial básico.
Hice unas cuantas pruebas y me gustó. Una de las cosas buenas de cmake es que puedes compilar en un directorio de compilación para no tener que ensuciar tu directorio de código, por ejemplo creas un directorio build dentro de src y dessde ahí ejecutas cmake ../ y te mete todo el código compilado y demás ficheros dentro del directorio build. También ofrece una salida en la compilación coloreada, que aunque parezca que no es una funcionalidad importante :P
Aquí está mi fichero CMakeList.txt dentro de src:
project(TBO) cmake_minimum_required(VERSION 2.8) set(DATA_DIR ${CMAKE_INSTALL_PREFIX}/share/tbo-data CACHE string "Shared directory") CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_SOURCE_DIR}/config.h) FIND_PACKAGE(GTK2) set(SRCS tbo.c ui-drawing.c ui-menu.c ui-toolbar.c ) INCLUDE_DIRECTORIES(${GTK2_INCLUDE_DIRS}) add_executable(tbo ${SRCS}) target_link_libraries(tbo ${GTK2_LIBRARIES})
Además de compilarte de forma fácil todos los ficheros .c cmake/autotools te permite generar ficheros (config.h) definiendo variables. Esto lo utilizo yo para definir el directorio donde están los datos, imágenes y demás. Es la variable DATA_DIR definida en el fichero de cmake. En este fichero config.h.cmake defino DATA_DIR con la variable del cmake:
#cmakdefine DATA_DIR "${DATA_DIR}"
Y si le paso la opción -DDATA_DIR=... puedo cambiar el directorio de dónde el código pillará las imágenes y los ficheros de datos. El cmake generará un fichero config.h con #define DATA_DIR "..."
En definitiva, que cmake facilita mucho el proceso de compilación y la búqueda de dependencias en diferentes plataformas.


Lo he estado pensando bien, y me he decidido por implementar TBO con C, con GTK+Cairo, en lugar de usar python.
La razón por la cual prefiero usar C es porque con python ya he realizado algunas aplicaciones de escritorio, y aunque esta añade elementos con los que no he tratado, como el uso de cairo, la mayor parte de la aplicación será en pygtk, y creo que esa tecnología ya la conozco suficiente.
Por otra parte está el lenguaje C, siempre me ha gustado. Es simple a la vez que potente y elegante, y desde que programo en python, C lo tengo más que abandonado, por eso estoy perdiendo soltura.
Así pues prefiero usar C que será más divertido e instructivo, y además como sigo usando gtk y cairo no implicará mucha más complicación que si lo hiciera con python.

Hoy edulix me ha pedido que le haga un logo para su nuevo proyecto en el CUSL4. El proyecto en concreto se llama RoboDo, y pretende acercar la potencia de los scripts a usuarios no programadores, presentando una interfaz gráfica y una manerá más o menos simple para generar "scripts" que actúen con el escritorio Plasma KDE :P
El proyecto se describe así:
RoboDo es una aplicación de KDE que permite crear flujos de trabajo gráficamente para automatizar tareas repetitivas.
Y este es el logo que he hecho:



Con el proyecto TBO pretendo hacer un editor fácil de usar para poder crear cómics, presentaciones guías de programas, etc.
La idea del proyecto es presentar una interfaz (hecha con gtk) más o menos sencilla que ofrezca un editor para que quién no sepa dibujar pueda hacer un cómic en cinco minutos para hacer una presentación de algo, un tutorial de cómo funciona algo o para dibujar un cómic gracioso.
Para ello el editor dispondrá de diferentes modelos prediseñados para ir añadiendo a las viñetas y el usuario tan sólo tendrá que colocar los personajes y escribir los bocadillos correspondientes para tener un cómic.
Quiero desarrollar el proyecto con python, gtk y cairo. Y tengo intención de que se pueda exportar a png y pdf y además quiero que añadir nuevos sets de personajes a añadir.

El otro día un amigo me preguntó como hacer consultas a una base de datos mysql desde python y le comenté que con sqlalchemy se podía hacer. Yo ya había hecho algunas cosas con sqlalchemy, pero no había tratado con bases de datos ya creadas, sino que siempre las creaba con sqlalchemy.
He estado mirando un poco en la web de sqlalchemy y me he encontrado SqlSoup (SqlAlchemy).
SqlSoup mapea una base de datos ya creada a objetos python, así que no es necesario crear las clases o declarar las tablas en código python, se infieren directamente de la base de datos ya creada.
Veamos como funciona con un simple ejemplo con sqlite.
-
Creamos la base de datos sqlite para las pruebas
- $ sqlite3 testdb.sqlite
- sqlite> create table users (name varchar(50), email varchar(100), primary key (name));
- sqlite> create table posts (id integer not_null auto_increment, user_id varchar(50),
- ...> text varchar, primary key (id), foreign key (user_id) references users(name));
-
Aquí un script de prueba para ejecutar
- # <a href="http://www.sqlalchemy.org/docs/05/reference/ext/sqlsoup.html<br />
- #" title="http://www.sqlalchemy.org/docs/05/reference/ext/sqlsoup.html<br />
- #">http://www.sqlalchemy.org/docs/05/reference/ext/sqlsoup.html<br />
- #</a> Mapeando una base de datos ya creada para usar desde python de
- # manera simple y rapida.
- # creamos la base de datos con sqlite3
- # sqlite3 testdb.sqlite
- # sqlite> create table users (name varchar(50), email varchar(100), primary key (name));
- # sqlite> create table posts (id integer not_null auto_increment, user_id varchar(50),
- # ...> text varchar, primary key (id), foreign key (user_id) references users(name));
- from sqlalchemy.ext.sqlsoup import SqlSoup, Session
- from sqlalchemy import or_, and_, desc
- #db = SqlSoup('mysql://scott:tiger@localhost/mydatabase')
- db = SqlSoup('sqlite:///testdb.sqlite')
- def add_users():
- user = db.users.insert(name='danigm', email='dani@exp.com')
- user2 = db.users.insert(name='dani', email='danigm@exp.com')
- user3 = db.users.insert(name='pepe', email='pepe@exp.com')
- user4 = db.users.insert(name='juan', email='juan@exp.com')
- db.flush()
- Session.commit()
- def add_posts():
- post1 = db.posts.insert(id=1, user_id='danigm', text='comentario1')
- post2 = db.posts.insert(id=2, user_id='danigm', text='comentario2')
- post3 = db.posts.insert(id=3, user_id='pepe', text='comentario3')
- post4 = db.posts.insert(id=4, user_id='juan', text='comentario4')
- db.flush()
- Session.commit()
- try:
- add_users()
- except:
- Session.rollback()
- try:
- add_posts()
- except:
- Session.rollback()
- print db.users.filter(db.users.name=='danigm').first().name
- where = or_(db.users.name=='danigm',
- db.users.email=='danigm@exp.com')
- users = db.users.filter(where).order_by(desc(db.users.name)).all()
- print users
- user = users[0]
- for post in db.posts.filter(db.posts.user_id == user.name):
- print user.name, post.text
-
Veamoslo paso a paso
- db = SqlSoup('sqlite:///testdb.sqlite')
- user = db.users.insert(name='danigm', email='dani@danigm.net')
- db.flush()
- Session.commit()
- print db.users.filter(db.users.name=='danigm').first().name
- where = or_(db.users.name=='danigm',
- db.users.email=='danigm@exp.com')
- users = db.users.filter(where).order_by(desc(db.users.name)).all()
- print users
- user = users[0]
- for post in db.posts.filter(db.posts.user_id == user.name):
- print user.name, post.text
Con esta simple línea tenemos el objeto db que está conectado con la base de datos y ya podemos empezar a hacer consultas u otras cosas.
Podemos acceder a las tablas por su nombre directamente, db.users, db.posts y estos objetos tienen métodos para insertar, borrar o filtrar de tal forma que podamos hacer la consulta deseada, en este ejemplo añadimos un usuario. Hay que tener en cuenta que para que los cambios se vean reflejados hay que ejecutar el commit().
se puede filtrar como en cualquier objeto de sqlite, en este ejemplo filtramos por nombre de usuario, nos quedamos con el primero y mostramos el nombre del mismo.
Y una vez dominado el uso de sqlalchemy se pueden hacer filtros más complejos con otras directivas. combinando filtros y usando toda la potencia que te da sqlalchemy.
Con este ejemplo vemos que acceder a bases de datos desde python es muy fácil con sqlalchemy y además puedes hacerlo independiente del motor de base de datos gracias a que sqlalchemy soporta mysql, postgresql, sqlite, oracle, firebird, MS-SQL, MSAccess.



