sábado, 16 de mayo de 2009

Crear una [pequeña] aplicación completa usando wxPython (Parte 2)

.
7 comentarios

Si has llegado aquí mediante un buscador o por simple casualidad, te cuento que estas en la segunda parte de un tutorial que empieza AQUI.

Como les anticipaba en la primera parte del tutorial, en esta entrega comenzaremos a trabajar en la Ventana Principal de nuestro catálogo; pero antes de empezar con esta tarea nos concertaremos en la creación del Modelo de la aplicación.
Si bien les había comentado brevemente de que se trata el patrón MVC, a continuación les transcribo una parte del articulo que habla sobre este tema en la Wikipedia, para comprender mejor las tareas que corresponden a cada uno de los componentes (Modelo, Vista y Controlador)

“ ... Aunque se pueden encontrar diferentes implementaciones de MVC, el flujo que sigue el control generalmente es el siguiente:

1. El usuario interactúa con la interfaz de usuario de alguna forma (por ejemplo, el usuario pulsa un botón, enlace, etc.)

2. El controlador recibe (por parte de los objetos de la interfaz-vista) la notificación de la acción solicitada por el usuario. El controlador gestiona el evento que llega, frecuentemente a través de un gestor de eventos (handler) o callback.

3. El controlador accede al modelo, actualizándolo, posiblemente modificándolo de forma adecuada a la acción solicitada por el usuario (por ejemplo, el controlador actualiza el carro de la compra del usuario). Los controladores complejos están a menudo estructurados usando un patrón de comando que encapsula las acciones y simplifica su extensión.

4. El controlador delega a los objetos de la vista la tarea de desplegar la interfaz de usuario. La vista obtiene sus datos del modelo para generar la interfaz apropiada para el usuario donde se refleja los cambios en el modelo (por ejemplo, produce un listado del contenido del carro de la compra). El modelo no debe tener conocimiento directo sobre la vista. Sin embargo, el patrón de observador puede ser utilizado para proveer cierta indirección entre el modelo y la vista, permitiendo al modelo notificar a los interesados de cualquier cambio. Un objeto vista puede registrarse con el modelo y esperar a los cambios, pero aun así el modelo en sí mismo sigue sin saber nada de la vista. El controlador no pasa objetos de dominio (el modelo) a la vista aunque puede dar la orden a la vista para que se actualice. Nota: En algunas implementaciones la vista no tiene acceso directo al modelo, dejando que el controlador envíe los datos del modelo a la vista.

5. La interfaz de usuario espera nuevas interacciones del usuario, comenzando el ciclo nuevamente. ...”

Tomando este texto como referencia, nos pondremos manos a la obra y a medida que vayamos avanzando con el código, les iré haciendo notar como se relaciona lo que estemos haciendo, con lo dicho en los 5 puntos anteriores.

Codificando el Modelo

La idea aquí sera crear una clase con una serie de métodos que nos permitan interactuar con nuestra base de datos, pudiendo realizar altas, bajas, modificaciones y consultas de las películas. Si no sabes como trabajar con una base de datos en Python, puedes leer mi tutorial sobre este tema (o cualquiera de los muchos que hay en la red). Dicho esto, veamos como podría ser nuestra primera versión del Modelo.

modelo.py

import MySQLdb

class Model:

def conectar(self):
self.cnn = MySQLdb.connect(host='localhost', user='user', passwd='user', db='catalogo_peliculas_db')
self.cursor = self.cnn.cursor()

def desconectar(self):
self.cnn.close()
self.cursor.close()

def agregarPelicula(self, titulo_pelicula, anio):
self.conectar()
self.cursor.execute("INSERT INTO tblPeliculas(TituloPelicula, Anio) VALUES('" + titulo_pelicula + "'," + anio + ")")

self.desconectar()

def modificarPelicula(self, id_pelicula, titulo_pelicula, anio):
self.conectar()
self.cursor.execute("UPDATE tblPeliculas SET TituloPelicula='" + titulo_pelicula + "', Anio=" + str(anio) + " WHERE IDPelicula=" + str(id_pelicula))

self.desconectar()

def eliminarPelicula(self, id_pelicula):
self.conectar()
self.cursor.execute("DELETE FROM tblPeliculas WHERE IDPelicula=" + str(id_pelicula))

self.desconectar()

def obtenerListadoDePeliculas(self):
self.conectar()

self.cursor.execute("SELECT * FROM tblPeliculas ORDER BY TituloPelicula")

listado = self.cursor.fetchall()

self.desconectar()

return listado

Dando un rápido vistazo sobre el código, vemos que nuestra clase llamada Model, tiene seis métodos que nos permiten conectarnos y desconectarnos de la base de datos; agregar, modificar y eliminar películas, y también obtener un listado de todas las películas del catálogo. Ahora que tenemos el modelo básico, haremos una referencia a una parte del punto cuatro del artículo de la Wikipedia que les cite al principio:

“4. …. El modelo no debe tener conocimiento directo sobre la vista. Sin embargo, el patrón de observador puede ser utilizado para proveer cierta indirección entre el modelo y la vista, permitiendo al modelo notificar a los interesados de cualquier cambio. Un objeto vista puede registrarse con el modelo y esperar a los cambios, pero aun así el modelo en sí mismo sigue sin saber nada de la vista. …"

Básicamente se nos esta diciendo que si bien el Modelo NO DEBE conocer nada sobre la vista (ni tampoco sobre el Controlador), el Modelo puede utilizar algún mecanismo para informar cuando se ha producido un cambio en él. Para implementar este “sistema de avisos”, nosotros vamos a hacer uso del modulo wx.lib.pubsub; el cual nos brinda la posibilidad de usar un componente de “publicación/suscripción” donde se “publica” un cierto mensaje, y este es recibido por quienes se hayan “suscripto” a él.

Veamos como quedaría el código anterior, con la opción de que se envíe un mensaje al modificarse el Modelo.

from wx.lib.pubsub import Publisher
import MySQLdb

class Model:

def conectar(self):
self.cnn = MySQLdb.connect(host='localhost', user='user', passwd='user', db='catalogo_peliculas_db')
self.cursor = self.cnn.cursor()

def desconectar(self):
self.cnn.close()
self.cursor.close()

def agregarPelicula(self, titulo_pelicula, anio):
self.conectar()
self.cursor.execute("INSERT INTO tblPeliculas(TituloPelicula, Anio) VALUES('" + titulo_pelicula + "'," + anio + ")")

self.desconectar()

Publisher.sendMessage("pelicula_agregada", None)

def modificarPelicula(self, id_pelicula, titulo_pelicula, anio):
self.conectar()
self.cursor.execute("UPDATE tblPeliculas SET TituloPelicula='" + titulo_pelicula + "', Anio=" + str(anio) + " WHERE IDPelicula=" + str(id_pelicula))

self.desconectar()

Publisher.sendMessage("pelicula_modificada", None)

def eliminarPelicula(self, id_pelicula):
self.conectar()
self.cursor.execute("DELETE FROM tblPeliculas WHERE IDPelicula=" + str(id_pelicula))

self.desconectar()

Publisher.sendMessage("pelicula_eliminada", None)

def obtenerListadoDePeliculas(self):
self.conectar()

self.cursor.execute("SELECT * FROM tblPeliculas ORDER BY TituloPelicula")

listado = self.cursor.fetchall()

self.desconectar()

return listado
Como se puede apreciar, es muy simple enviar un mensaje avisando de que se ha producido un cambio en el Modelo, en nuestro caso, enviamos el mensaje “pelicula_agregada”, “pelicula_modificada” y “pelicula_eliminada” según corresponda; para lo cual hacemos uso del método sendMessage del objeto Publisher (que previamente hemos importado). Mas adelante veremos como hacemos para suscribirnos a estos mensajes, pero lo que se debe tener claro, es que si bien el Modelo envía los mensajes, él realmente no sabe quien los recibirá.

Hasta aquí hemos llegado por ahora con el Modelo; mas tarde le iremos agregando mayor funcionalidad a medida que vaya surgiendo la necesidad.

A continuación, empezaremos a trabajar en la Ventana Principal.

Codificando la Ventana Principal

Para codificar la Ventana Principal (y para todas las ventanas que crearemos) la debemos pensar en dos partes: la Vista y el Controlador. Teniendo esto presente, crearemos en primer lugar una clase llamada VistaPrincipal:

vistaPrincipal.py

import wx
from wx import xrc

class VistaPrincipal:
def __init__(self, app):
self.app = app

self.res = self.app.res
self.frame = self.res.LoadFrame(None, 'FramePrincipal')

def mostrar(self):
self.frame.Show()

def cargarListadoDePeliculas(self):
self.listaDePeliculas = xrc.XRCCTRL(self.frame, 'listPeliculas')

self.listaDePeliculas.SetSingleStyle(wx.LC_REPORT, True)

self.listaDePeliculas.InsertColumn(0, 'Tí­tulo', format=wx.LIST_FORMAT_LEFT, width=-1)
self.listaDePeliculas.InsertColumn(1, 'Año', format=wx.LIST_FORMAT_LEFT, width=-1)

peliculas = self.app.modelo.obtenerListadoDePeliculas()

#Cargo el listado de películas

index = 0
for
pelicula in peliculas:

item = self.listaDePeliculas.InsertStringItem (index, str(pelicula[1]))
self.listaDePeliculas.SetItemData(item, pelicula[0])

self.listaDePeliculas.SetStringItem(index, 1, str(pelicula[2]))

index+=1

self.listaDePeliculas.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self.listaDePeliculas.SetColumnWidth(1, wx.LIST_AUTOSIZE)
Esta clase representa la parte gráfica de la Ventana Principal. Al ser instanciada, recibe como parámetro el objeto aplicación (que mas tarde crearemos), el cual representa la aplicación propiamente dicha, y es la que posee la información acerca de cual es el Modelo de la aplicación y nos permite acceder a los recursos graficos que carga desde el archivo XRC que creamos al principio del tutorial.

Ahora veamos en detalle los métodos de esta clase:

__init__

Aquí obtenemos los recursos gráficos desde el objeto app que recibimos como parámetro

self.app = app
self.res = self.app.res

y luego cargamos el Frame de la Ventana Principal (FramePrincipal)

self.frame = self.res.LoadFrame(None, 'FramePrincipal')

mostrar

Este método, simplemente hace visible la Ventana Principal.

self.frame.Show()

cargarListadoDePeliculas

Como el nombre del método lo indica, aquí se carga el listado de las películas. Lo primero que hacemos es obtener una referencia al objeto listPeliculas (wxListControl)

self.listaDePeliculas = xrc.XRCCTRL(self.frame, 'listPeliculas')

luego ponemos la lista en “modo reporte”

self.listaDePeliculas.SetSingleStyle(wx.LC_REPORT, True)

agregamos las columnas de Titulo y Año:

self.listaDePeliculas.InsertColumn(0, 'Título', format=wx.LIST_FORMAT_LEFT, width=-1)

self.listaDePeliculas.InsertColumn(1, 'Año', format=wx.LIST_FORMAT_LEFT, width=-1)

accedemos al Modelo para obtener el listado de las películas:

self.app.modelo.obtenerListadoDePeliculas()

cargamos las películas en la lista

index = 0
for pelicula in peliculas:
item = self.listaDePeliculas.InsertStringItem (index, str(pelicula[1]))
self.listaDePeliculas.SetItemData(item, pelicula[0])

self.listaDePeliculas.SetStringItem(index, 1, str(pelicula[2]))

index+=1

y por último, ajustamos el ancho de las columnas para que sea óptimo.

self.listaDePeliculas.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self.listaDePeliculas.SetColumnWidth(1, wx.LIST_AUTOSIZE)

Esta clase esta relacionada con el punto 1 del texto que extrajimos de la Wikipedia:

“1. El usuario interactúa con la interfaz de usuario de alguna forma (por ejemplo, el usuario pulsa un botón, enlace, etc.)”

Si bien queda claro que esta clase se encarga de todo lo referido a la parte visual de la Ventana Principal, y permite que el usuario interaccione con nuestra aplicación, también queda claro que falta que de algún modo estemos a la escucha de los eventos que genera el usuario para actuar en consecuencia; por ejemplo, si hace click en el botón “Agregar” de la barra de herramientas, debería aparecer el Editor de Películas, o si pulsa el botón “Eliminar”, se debería eliminar la película seleccionada y así con todas las opciones. Aquí es donde entra en juego el Controlador, que sera el encargado de recibir los eventos generados por el usuario sobre la interface (pulsaciones de teclado, pulsaciones y movimientos del mouse, etc.) y de este manera realizar la opción solicitada.

Por ahora llegamos hasta aquí, y en la próxima parte nos meteremos de lleno en el Controlador de la Ventana Principal, y veremos como hacer que nuestra aplicación pueda ser ejecutada, y muestre esta ventana con el listado de películas cargado.

Como siempre, pueden comentar las dudas que tengan, sugerir alguna mejora, informar algun error o simplemente decir algo.

Saludos!

lunes, 4 de mayo de 2009

Crear una [pequeña] aplicación completa usando wxPython (Parte 1)

.
2 comentarios

Como les adelantaba en el post anterior, hoy comenzaré a publicar un tutorial de varias partes, en donde veremos como desarrollar una pequeña aplicación "completa" con wxPython y archivos XRC. El proyecto consistirá en realizar un catálogo de películas. El catálogo permitirá agregar, modificar y eliminar películas, así como hacer búsquedas dentro del listado. La parte gráfica la haremos haciendo uso de lo aprendido en tutoriales anteriores, usando archivos de recursos que realizaremos con wxFormBuilder y la información la guardaremos en una base de datos MySQL. Dicho esto, y sin entrar en mas detalles, empecemos a trabajar.

Estructurando la aplicación

Para estructurar nuestra aplicación, haremos uso del patrón MVC (Modelo-Vista-Controlador). Por si no sabes de que hablo, te comento brevemente de que se trata.
MVC es un patrón de diseño de software que propone dividir nuestra aplicación en tres componentes básicos: Modelo, Vista y Controlador. Esta forma de dividir la aplicación simplifica muchísimo la escalabilidad y el mantenimiento de la misma, pero también es cierto que a veces puede ser un tanto tediosa implementarla en proyectos pequeños. Si bien, nuestra aplicación entra en la categoría de proyecto pequeño, es una buena idea pensar a futuro, y generar buenos cimientos para futuras expansiones. Existen una gran cantidad de implementaciones del patrón MVC, e incluso a veces cada componente (Modelo, Vista y Controlador) pueden utilizar a su vez otro patrón de diseño en su interior, pero lo importante es comprender la idea básica detrás de MVC, y adaptarla a nuestras necesidades. A continuación veremos brevemente (y a grandes rasgos), cual es la función de cada componente del patrón MVC:

Modelo:

Este componente es el responsable de manipular los datos de nuestra aplicación. En nuestro caso será el encargado de permitirnos el acceso a una base de datos MySQL que contendrá el catálogo propiamente dicho. El modelo también es el responsable de implementar las reglas de negocio en la aplicación y es quien al producirse alguna modificación en los datos, puede comunicar este cambio a los otros componentes para que actúen en consecuencia (Por ej.: tras agregar un registro en una base de datos, se podría querer recargar el listado que esta viendo el usuario de modo que refleje los datos actualizados).

Vista:

La Vista es la encargada de presentar la información del Modelo de modo que se pueda interactuar con éste. La Vista puede quedarse a la "escucha" de cambios en el Modelo, para que al producirse cambios en él, la Vista se adapte a los nuevos datos.

Controlador:

El controlador hace de nexo entre la Vista y el Modelo, disparando acciones en respuesta a los eventos que ocurran en la aplicación, pudiendo invocar acciones en el Modelo y en la Vista.

Esta división de la aplicación en varios componentes, nos brinda una gran independencia entre los datos concretos (el Modelo) y como se ve la aplicación (la Vista), pudiendo tener varias Vistas de un mismo Modelo, y de este modo por ejemplo generar sobre un mismo Modelo, una Vista que nos permita acceder desde una PC y otra optimizada para acceder desde dispositivos móviles, o tal vez una Vista que nos permita acceder al Modelo vía una interfaz web, entre muchos otros escenarios posibles.

Este patrón de diseño no es el único, ni mucho menos, pero los demás quedan fuera del alcance de este tutorial, e incluso te invito a que profundices por ti mismo, sobre las distintas implementaciones y variaciones del patrón MVC.
Creo que con esta breve introducción, estamos en condiciones de comenzar con el desarrollo de la aplicación propiamente dicha. No aseguro que esta sea la mejor implementación de MVC; y los invito a que hagan sugerencias para mejorarla.

La Base de datos

Como ya les anticipe, para guardar los datos de nuestro catálogo haremos uso de MySQL. Nuestra base de datos será realmente muy simple, y por el momento tendrá tan sólo una tabla con tres campos.
El nombre de la base de datos será catalogo_peliculas_db y la única tabla que (por ahora) tendremos se llamará tblPeliculas. Para que vean los campos que tendrá la tabla, les muestro una captura de su estructura, vista con phpMyAdmin:

Claramente podríamos guardar muchos mas datos sobre cada película (director, elenco, nombre original, nacionalidad, etc.), pero por ahora no lo vamos a hacer para mantener la aplicación lo mas simple posible.

Ahora que ya tenemos el diseño de nuestra base de datos, pasemos a diseñar la interface gráfica de la aplicación.

La interface gráfica

Para crear la interface gráfica que le permita a los usuarios interactuar con nuestra aplicación; haremos uso (una vez mas) de wxFormBuilder. No es necesario que uses esta herramienta, sino que puedes usar cualquier otro editor de archivos XRC (incluso lo puedes hacer con un simple editor de textos). Si no sabes que es un archivo XRC o no sabes como generarlo, te invito a que leas mis tutoriales sobre organización de widgets en wxPython y manejo de archivos XRC con wxPython.

Por ahora, nuestra aplicación se compondrá sólo de dos ventanas: la "Ventana Principal" y el "Editor de Películas".

La Ventana Principal

La versión inicial de nuestra Ventana Principal será la siguiente:

Como pueden ver, nuestra Ventana Principal se compone de un menú, una barra de herramientas y una lista que muestra las películas que tenemos en nuestra base de datos.
A continuación les muestro una captura del Object Tree de wxFormBuilder para que vean claramente los widgets que la componen, y los nombres de cada uno de ellos.


El menú de nuestra aplicación desplegado se ve así:


Como se puede ver, el menú presenta las mismas opciones que la barra de herramientas, excepto la opción "Buscar" y se le añade la opción "Salir". Mas adelante, tal vez vayamos agregando nuevas opciones a nuestra interface, pero por el momento, con esto tenemos bastante para trabajar.

Ahora veamos la interfaz del Editor de Películas.

El Editor de Películas

A través del Editor de Películas, podremos agregar y modificar las películas del catálogo. Si "llamamos" a esta ventana desde la opción Agregar (ya sea desde la barra de herramientas o desde el menú), el Editor aparecerá con los campos en blanco, de modo que carguemos los datos de la película que queremos añadir al catálogo. En este caso, la pantalla aparecería así:

En cambio, si invocamos al Editor de Películas desde la opción Editar (tanto desde la barra de herramientas como desde el menú), este presentara los datos de la película seleccionada en el listado de la Ventana Principal, para de este modo, modificar la información necesaria y guardar los cambios. En este escenario, la pantalla se vería así:


Si bien tanto para agregar como para modificar una película, utilizamos el mismo editor, pueden notar que el texto del botón izquierdo cambia de Agregar a Guardar según en que modo se encuentra.

Al igual que con la Ventana Principal, a continuación les muestro como queda el Object Tree de wxFormBuilder para esta ventana:


IMPORTANTE:
Las dos ventanas estarán dentro de un mismo archivo XRC llamado gui.xrc.

Hasta aquí hicimos una breve presentación de la aplicación y de como la vamos a estructurar. En la próxima entrega comenzaremos a codificar la Ventana Principal y de este modo le iremos "dando vida" a nuestro pequeño catálogo.

Hasta la próxima!

Editado:
Ya esta disponible la parte II