lunes, 1 de febrero de 2010

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

.
15 comentarios

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

Como les había adelantado en la parte anterior, hoy empezaremos a trabajar en el Editor de Películas del Catálogo. Este editor sera el medio por el cual los usuarios podrán tanto agregar películas al catálogo, como editar la información de las que ya se encuentren cargadas. Tal como hicimos con la Ventana Principal, crearemos por un lado la Vista y por otro el Controlador. Dicho esto, empecemos a trabajar.

Codificando la Vista del Editor de películas

La clase que representa la Vista de nuestro Editor la llamaremos VistaEditorDePeliculas. Como les había comentado, el Editor sera invocado para realizar dos funciones distintas: agregar o modificar una película. Para que el Editor “sepa” en que modo lo estamos llamando, le pasaremos un parámetro. El parámetro lo llamaremos agregar y tendrá como valor por defecto True; es decir que si al invocarlo, no especificamos nada, el Editor se abrirá en modo “Agregar Película”. Además del modo en que queremos que se muestre el Editor, también le diremos quien es su ventana padre (en nuestro caso sera la Ventana Principal), y también recibirá una referencia a la aplicación en si, para poder acceder a los recursos (app.res) desde donde cargaremos la interface gráfica del dialogo, y también podremos saber cual es el modelo (app.modelo) de la aplicación. A continuación les muestro el código que implementa lo que les acabo de comentar:
import wx
from wx import xrc

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

Con esto tenemos nuestro método __init__ casi terminado. Para finalizar agregaremos algo de código, para poder determinar si estamos en modo “Agregar” o “Editar”, y establecer el label del botón que guarda los cambios (btnAceptar) con el texto “Agregar” o “Guardar” según corresponda:
import wx
from wx import xrc

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

btnAgregarPelicula = xrc.XRCCTRL(self.Dialog, 'btnAceptar')

if(agregar):
btnAgregarPelicula.SetLabel('Agregar')
else:
btnAgregarPelicula.SetLabel('Guardar')

Por otro lado, debemos tener en cuenta que en caso que el Editor sea abierto en modo “Agregar”, basta con que el dialogo se muestre, pero si se abre en modo “Editar”, los controles del dialogo deben estar cargados con los datos correspondientes a la película que esta seleccionada en la Ventana Principal; para llevar a cabo esta tarea, codificaremos el método cargarPelicula. Este método recibirá el ID que tiene asignado la película en la base de datos, y haciendo uso del Modelo obtendremos los datos de esta, para luego cargar los controles correspondientes del Editor. Veamos como quedaría:
import wx
from wx import xrc

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

btnAgregarPelicula = xrc.XRCCTRL(self.Dialog, 'btnAceptar')

if(agregar):
btnAgregarPelicula.SetLabel('Agregar')
else:
btnAgregarPelicula.SetLabel('Guardar')

def cargarPelicula(self, idPelicula):
infoPelicula = pelicula.Pelicula()

infoPelicula = self.modelo.obtenerInformacionDePelicula(idPelicula)

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
anio_pelicula = xrc.XRCCTRL(self.Dialog, 'txtAnio')

titulo_pelicula.SetValue(infoPelicula.getTitulo())
anio_pelicula.SetValue(infoPelicula.getAnio())
Como pueden ver, el método cagarPelicula, llama al método obtenerInformacionDePelicula del Modelo, pasándole el ID de la película cuyos datos necesitamos. Tras esta llamada, tendremos el resultado en infoPelicula, y con estos cargamos los controles correspondientes al titulo y el año de la misma.

Por si no lo recuerdan, el método obtenerInformacionDePelicula al que estamos llamando, aún no existe en nuestro Modelo, así que veamos como sería el código de este:

def obtenerInformacionDePelicula(self, idPelicula):
self.conectar()

self.cursor.execute("SELECT * FROM tblPeliculas WHERE IDPelicula = " + str(idPelicula))

resultado = self.cursor.fetchone()

infoPelicula = pelicula.Pelicula()

infoPelicula.setTitulo(str(resultado[1]))
infoPelicula.setAnio(str(resultado[2]))

self.desconectar()

return infoPelicula
Este método, tan sólo ejecuta una consulta a la base de datos para obtener la información de la película cuyo ID coincida con el que le enviamos como parámetro, y luego retorna esta información a quien lo haya invocado.

Hasta ahora no tenemos la posibilidad de que se muestre el Editor, así que agregaremos un método a la Vista, llamado Mostrar para llevar a cabo esta tarea:
import wx
from wx import xrc

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

btnAgregarPelicula = xrc.XRCCTRL(self.Dialog, 'btnAceptar')

if(agregar):
btnAgregarPelicula.SetLabel('Agregar')
else:
btnAgregarPelicula.SetLabel('Guardar')

def cargarPelicula(self, idPelicula):
infoPelicula = pelicula.Pelicula()

infoPelicula = self.modelo.obtenerInformacionDePelicula(idPelicula)

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
anio_pelicula = xrc.XRCCTRL(self.Dialog, 'txtAnio')

titulo_pelicula.SetValue(infoPelicula.getTitulo())
anio_pelicula.SetValue(infoPelicula.getAnio())

def Mostrar(self):
self.Dialog.ShowModal()
Luego de terminar con esta clase, pasaremos a desarrollar el Controlador del Editor, el cual se encargara entre otras cosas de enviar a guardar los datos ingresados en los controles del Editor; es por esto que necesitamos crear un método en la Vista, que retorne lo que el usuario haya cargado; así que pasaremos a agregar el método getInfoPelicula:

import wx
from wx import xrc

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

btnAgregarPelicula = xrc.XRCCTRL(self.Dialog, 'btnAceptar')

if(agregar):
btnAgregarPelicula.SetLabel('Agregar')
else:
btnAgregarPelicula.SetLabel('Guardar')

def Mostrar(self):
self.Dialog.ShowModal()

def cargarPelicula(self, idPelicula):
infoPelicula = pelicula.Pelicula()

infoPelicula = self.modelo.obtenerInformacionDePelicula(idPelicula)

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
anio_pelicula = xrc.XRCCTRL(self.Dialog, 'txtAnio')

titulo_pelicula.SetValue(infoPelicula.getTitulo())
anio_pelicula.SetValue(infoPelicula.getAnio())

def getInfoPelicula(self):
infoPelicula = pelicula.Pelicula()

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
anio_pelicula = xrc.XRCCTRL(self.Dialog, 'txtAnio')

infoPelicula.setTitulo(titulo_pelicula.GetValue())
infoPelicula.setAnio(anio_pelicula.GetValue())

return infoPelicula
El método getInfoPelicula, simplemente obtiene los valores que el usuario introdujo en los controles y los carga en un objeto de la clase Pelicula, para luego retornarlo como resultado de su ejecución. Aun no hemos creado la clase Pelicula, así que pasemos a ver como seria:
class Pelicula:
def __init__(self):
self.titulo = ""
self.anio = ""

def setTitulo(self, titulo):
self.titulo = titulo

def getTitulo(self):
return self.titulo

def setAnio(self, anio):
self.anio = anio

def getAnio(self):
return self.anio
De este modo, damos por terminada la Vista del Editor de películas y nos metemos de lleno con su Controlador.

Codificando el Controlador del Editor de películas

El controlador del Editor sera el encargado de enviar a agregar o modificar la película según en que estado se encuentre abierto el Editor, es por esto que crearemos en este, los métodos onAgregarPelicula y onModificarPelicula:

def onAgregarPelicula(self, evt):
infoPelicula = self.vista.getInfoPelicula()
self.app.modelo.agregarPelicula(infoPelicula.getTitulo(), infoPelicula.getAnio())

def onModificarPelicula(self, evt):
infoPelicula = self.vista.getInfoPelicula()
self.app.modelo.modificarPelicula(self.idPelicula, infoPelicula.getTitulo(), infoPelicula.getAnio())
Claramente los dos métodos son muy parecidos, simplemente obtienen los datos cargados en la Vista a través del método getInfoPelicula que codificamos anteriormente, y llaman a al método agregarPelicula o modificarPelicula del Modelo según corresponda.

Teniendo en cuenta que tenemos dos acciones posibles, debemos “asociar” el botón btnAceptar con una u otra función a la hora de crear la clase, es por esto que en el método __init__ (y al igual que hacíamos con la Vista) le pasamos el parámetro Agregar para determinar en que estado queremos mostrar el Editor, y así llevar a cabo la asociación. Para clarificar esto, veamos como quedaría la clase completa:

from vistas.vistaEditorDePelicula import VistaEditorDePelicula
import wx
from wx import xrc
import pelicula

class ControladorEditorDePelicula:
def __init__(self, app, parent, agregar=True):
self.app = app> self.vistaPrincipal = parent
self.vista = VistaEditorDePelicula(self.vistaPrincipal.frame, self.app, agregar)
btnAgregarPelicula = xrc.XRCCTRL(self.vista.Dialog, 'btnAceptar')

if(agregar):

self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onAgregarPelicula, btnAgregarPelicula)

else:

self.idPelicula = app.controladorVistaPrincipal.vista.getIDItemSeleccionado()

self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onModificarPelicula, btnAgregarPelicula)

self.vista.cargarPelicula(self.idPelicula)

self.vista.Mostrar()

def onAgregarPelicula(self, evt):
infoPelicula = self.vista.getInfoPelicula()
self.app.modelo.agregarPelicula(infoPelicula.getTitulo(), infoPelicula.getAnio())

def onModificarPelicula(self, evt):
infoPelicula = self.vista.getInfoPelicula()
self.app.modelo.modificarPelicula(self.idPelicula, infoPelicula.getTitulo(), infoPelicula.getAnio())

Pueden ver que el método __init__ comienza creando la Vista (a la cual le pasa los parámetros que tratamos oportunamente), luego determina si estamos en el modo “Agregar” o “Editar” y asocia el botón btnAceptar con el método onAgregarPelicula o onModificarPelicula; y por ultimo hace visible el Editor invocando el método Mostrar de la Vista del Editor. Para terminar, tan solo quiero que presten atención que si estamos en el modo de edición, obtenemos el ID de la película seleccionada en la Ventana Principal, y llamamos al método cargarPelicula de la Vista del Editor pasándoselo como parámetro, de este modo logramos que cuando aparezca el Editor en pantalla, este presente los datos de la película que el usuario quiere modificar.

Con esto terminamos esta cuarta parte del tutorial. En la próxima entrega haremos algunos cambios estéticos a nuestra pequeña aplicación (Agregaremos algunos iconos a la barra de herramientas, menues y botones) y también veremos como hacer para que podamos movernos entre las distintas películas del Catálogo directamente desde el Editor de Películas, sin necesidad de estar cerrándolo para pasar a modificar otra película.

jueves, 25 de junio de 2009

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

.
10 comentarios

En esta oportunidad crearemos el Controlador de la Vista Principal, y por primera vez podremos ejecutar nuestra aplicación. Sin mas preámbulo, empecemos!.

Codificando el Controlador de la Vista Principal

Para comenzar, voy a citar el punto 2 del artículo de la Wikipedia que les comentaba en la parte anterior del tutorial.

"… 1. 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. ..."

Este punto nos esta diciendo:

1. El Controlador recibe desde la Vista, la notificación de la acción solicitada por el usuario (ej: "se pulso el botón Salir", "se pulso el botón Agregar Película", etc.).

2. El Controlador gestiona el evento, haciendo una llamada a quien corresponda para que atienda esa acción en particular (ej: "Si el usuario pulso el botón Agregar Película llamar al método onAgregarPelicula, para que se encargue de mostrar el Editor de Películas").

Teniendo estos dos puntos presentes, nuestro Controlador para la Vista Principal, podría (inicialmente) ser algo así:

controladorVistaPrincipal.py
import wx

from wx import xrc

from vistas.vistaPrincipal import VistaPrincipal

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

self.vista = VistaPrincipal(app)

#Items del Menú
self.vista.frame.Bind(wx.EVT_MENU, self.onMostrarAgregarPelicula, id=xrc.XRCID('itemAgregarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onMostrarEditorDePelicula, id=xrc.XRCID('itemEditarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onEliminarPelicula, id=xrc.XRCID('itemEliminarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onSalir, id=xrc.XRCID('itemSalir'))

#Botones de la barra de herramientas
self.vista.frame.Bind(wx.EVT_TOOL, self.onMostrarAgregarPelicula, id=xrc.XRCID('toolAgregarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onMostrarEditorDePelicula, id=xrc.XRCID('toolEditarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onEliminarPelicula, id=xrc.XRCID('toolEliminarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onBuscarPelicula, id=xrc.XRCID('toolBuscarPelicula'))

def onMostrarAgregarPelicula(self, evt):
...

def onMostrarEditorDePelicula(self, evt):
...

def onEliminarPelicula(self, evt):
...

def onBuscarPelicula(self, evt):
...

def onSalir(self, evt):
self.app.Exit()

Lo primero que hacemos en esta clase es crear la Vista Principal de la aplicación (aunque aún no la mostramos).

self.vista = VistaPrincipal(app)

después "enlazamos" los eventos que nos interesan con su correspondiente manejador de evento. Primero lo hacemos para cuando sean activados los items del menú

#Items del Menú
self.vista.frame.Bind(wx.EVT_MENU, self.onMostrarAgregarPelicula, id=xrc.XRCID('itemAgregarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onMostrarEditorDePelicula, id=xrc.XRCID('itemEditarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onEliminarPelicula, id=xrc.XRCID('itemEliminarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onSalir, id=xrc.XRCID('itemSalir'))

y luego para cuando sean pulsados los botones de la barra de herramientas

#Botones de la barra de herramientas
self.vista.frame.Bind(wx.EVT_TOOL, self.onMostrarAgregarPelicula, id=xrc.XRCID('toolAgregarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onMostrarEditorDePelicula, id=xrc.XRCID('toolEditarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onEliminarPelicula, id=xrc.XRCID('toolEliminarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onBuscarPelicula, id=xrc.XRCID('toolBuscarPelicula'))

Pueden observar que algunos manejadores de eventos son llamados como respuesta a mas de un evento, por ej: tanto el ítem del menú "Eliminar" como el botón "Eliminar" de la barra de herramientas, están asociados al manejador de eventos onEliminarPelicula.

Como ultimo paso, creamos (a medias) los distintos manejadores de eventos que serán llamados según las acciones que solicite el usuario.

def onMostrarAgregarPelicula(self, evt):
...

def onMostrarEditorDePelicula(self, evt):
...

def onEliminarPelicula(self, evt):
...

def onBuscarPelicula(self, evt):
...

def onSalir(self, evt):
self.app.Exit()

Ahora haremos referencia al punto 2 del artículo de la Wikipedia, para seguir creando nuestro Controlador

"... 2. 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. ..."

Leyendo este punto, vemos que nuestro Controlador se encarga de acceder al Modelo y hacer las modificaciones necesarias. En nuestro caso la tarea de agregar y eliminar una película no estará a cargo de este Controlador sino a cargo del Controlador de la Vista del Editor de Películas, pero en el caso de que se quiera eliminar una película, esta acción si será llevada a cabo por el método onEliminarPelicula de este Controlador, y sería mas o menos así:

def onEliminarPelicula(self, evt):
id_pelicula = self.vista.getIDItemSeleccionado()

self.app.modelo.eliminarPelicula(id_pelicula)

así de simple es la tarea de eliminar la película que este seleccionada en el listado de la Vista Principal. Primero obtenemos el ID de la película seleccionada con el método getIDItemSeleccionado de la Vista Principal y luego llamamos al método onEliminarPelicula del Modelo pasándole el ID de la película que deseamos borrar, pero por si no lo recuerdan, el método getIDItemSeleccionado aún no lo hemos creado, así que debemos agregar el siguiente código a la clase VistaPrincipal:

def getIDItemSeleccionado(self):
itemIndex = self.listaDePeliculas.GetFirstSelected()
return self.listaDePeliculas.GetItemData(itemIndex)

en este método obtenemos un referencia al ítem seleccionado y luego retornamos el ID que esta asociado a éste.

Por ahora no nos estamos preocupando por las validaciones, pero mas adelante volveremos sobre esto. También deberíamos pedir una confirmación antes de borrar la película, e incluso podríamos dar la posibilidad de borrar de a mas de una película al mismo tiempo, pero por el momento quedará pendiente.

Los métodos onMostrarAgregarPelicula y onMostrarEditorDePelicula serán (justamente) los encargados de mostrar el Editor de Películas en modo Agregar o Modificar respectivamente. Como aún no tenemos ni la Vista ni el Controlador del Editor de Películas, por ahora haremos que sólo se muestre un mensaje diciendo qué debería suceder al ser llamados, es decir que quedarían así:

def onMostrarAgregarPelicula(self, evt):
wx.MessageBox("Mostrar el Editor de Películas para agregar una película")

def onMostrarEditorDePelicula(self, evt):
wx.MessageBox("Mostrar el Editor de Películas para modificar la película seleccionada")

También hacemos lo mismo con el método onBuscarPelicula:

def onBuscarPelicula(self, evt):
wx.MessageBox("Buscar una película")

En este punto nos referiremos a una parte del punto 4 del artículo que hemos tomado como referencia:

"... 4. … 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. ..."

De las dos implementaciones que nos habla este punto, nosotros usaremos la primera, es decir que el Controlador no pasará información del Modelo a la Vista, sino que el Controlador sólo avisará cuando haya alguna modificación en el Modelo, y será la Vista la encargada de actualizarse. Teniendo esto claro, necesitamos algún mecanismo para que el Controlador "se entere" de que el Modelo ha cambiado. Si hacemos un poco de memoria, recordaremos que el modelo "avisaba" cuando se agregaba, modificaba o eliminaba una película haciendo uso del modulo wx.lib.pubsub, a través del método sendMessage() del objeto Publisher, o sea que lo único que nos quedaría por hacer es suscribirnos a esos mensajes y así quedarnos a la escucha de estos. Para llevar a cabo esta tarea, simplemente utilizamos el método suscribe() del objeto Publisher de la siguiente manera:

Publisher.subscribe(self.seModificoElModelo, 'pelicula_agregada')
Publisher.subscribe(self.seModificoElModelo, 'pelicula_modificada')
Publisher.subscribe(self.seModificoElModelo, 'pelicula_eliminada')

Con estas tres lineas, lo que estamos haciendo es quedarnos a la escucha de los mensajes 'pelicula_agregada', 'pelicula_modificada' y 'pelicula_eliminada', y de ser recibidos, llamamos (por ahora) al método seModificoElModelo, que codificaremos así:

def seModificoElModelo(self, mensaje):
self.vista.cargarListadoDePeliculas()

Lo único que estamos haciendo aquí, es invocar el método cargarListadoDePeliculas() de la Vista Principal, con lo cual se recargarían las películas y así se vería reflejado el cambio que se ha producido en el Modelo (se agrego, modifico o elimino una película).

Por último, una vez que nos suscribimos a los mensajes que nos interesan, nos queda cargar el listado de películas y hacer visible la Vista:

self.vista.cargarListadoDePeliculas()
self.vista.Mostrar()

A continuación les muestro como queda la clase completa hasta ahora:

import wx

from wx import xrc
from wx.lib.pubsub import Publisher

from vistas.vistaPrincipal import VistaPrincipal

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

#Items del Menú
self.vista.frame.Bind(wx.EVT_MENU, self.onMostrarAgregarPelicula, id=xrc.XRCID('itemAgregarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onMostrarEditorDePelicula, id=xrc.XRCID('itemEditarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onEliminarPelicula, id=xrc.XRCID('itemEliminarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onSalir, id=xrc.XRCID('itemSalir'))

#Botones de la barra de herramientas
self.vista.frame.Bind(wx.EVT_TOOL, self.onMostrarAgregarPelicula, id=xrc.XRCID('toolAgregarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onMostrarEditorDePelicula, id=xrc.XRCID('toolEditarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onEliminarPelicula, id=xrc.XRCID('toolEliminarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onBuscarPelicula, id=xrc.XRCID('toolBuscarPelicula'))

#Me suscribo a los mensajes que indican que se modifico el Modelo
Publisher.subscribe(self.seModificoElModelo, 'pelicula_agregada')
Publisher.subscribe(self.seModificoElModelo, 'pelicula_modificada')
Publisher.subscribe(self.seModificoElModelo, 'pelicula_eliminada')

#Cargo la lista de peliculas
self.vista.cargarListadoDePeliculas()

self.vista.Mostrar()

def onMostrarAgregarPelicula(self, evt):
wx.MessageBox("Mostrar el Editor de Peliculas para agregar una pelicula")

def onMostrarEditorDePelicula(self, evt):
wx.MessageBox("Mostrar el Editor de Peliculas para modificar la pelicula seleccionada")

def onEliminarPelicula(self, evt):
id_pelicula = self.vista.getIDItemSeleccionado()
self.app.modelo.eliminarPelicula(id_pelicula)

def onBuscarPelicula(self, evt):
wx.MessageBox("Buscar una pelicula")

def onSalir(self, evt):
self.app.Exit()
Con esto tenemos el Modelo, y la Vista y el Controlador de la Ventana Pricipal en condiciones de ser ejecutados, pero aún nos falta crear la clase que representa la aplicación, que será el punto de partida para que nuestro Catálogo puede iniciarse.

Codificando la clase de la Aplicación

Primero les mostrare el código de esta pequeña clase, y luego iremos paso a paso viendo que es lo que hace:

catalogo.py
import wx
from wx import xrc
from controladores.controladorVistaPrincipal import ControladorVistaPrincipal
from modelo import Model

class CatalogoDePeliculas(wx.App):
def OnInit(self):
self.modelo = Model()
self.res = xrc.XmlResource('gui.xrc')

self.controladorVistaPrincipal = ControladorVistaPrincipal(self)

return True

app = CatalogoDePeliculas()
app.MainLoop()

En primer lugar vemos que dentro de los modulos que importamos, se encuentran el Controlador de la Vista Principal (ControladorVistaPrincipal) y el Modelo (Model). También notamos que el nombre de la clase es CatalogoDePeliculas y que esta deriva de wx.App. Ya en el método OnInit, vemos que instanciamos self.modelo con el Modelo de la aplicación:

self.modelo = Model()

luego, cargamos el archivo de recursos

self.res = xrc.XmlResource('gui.xrc')

creamos el Controlador de la Vista Principal, al cual le pasamos la propia aplicación como parámetro

self.controladorVistaPrincipal = ControladorVistaPrincipal(self)

y ya fuera de la clase, creamos la aplicación e iniciamos el bucle principal.

De este modo, estamos listos para poder ver nuestra aplicación en marcha, ejecutando este archivo (catalogo.py). Si bien aún no podemos editar las películas del catálogo, podriamos agregar algunas películas en forma manual dentro de la base de datos, y al ejecutar el catálogo veríamos que estas se cargarían en el listado, e incluso las podríamos eliminar.

Hasta aquí hemos llegado en esta entrega. En la próxima trabajaremos en el Editor de Películas y así seguiremos agregando funcionalidad a nuestro pequeño Catálogo de películas.

Saludos!

Cómo usar Blogger con un dominio personalizado (.com.ar)? [Actualizado - Junio de 2009]

.
66 comentarios

Importante:

Dado que ha habido un cambio en la configuración de dominios personalizados en Blogger, he creado este nuevo post actualizado (Junio de 2009) para reflejar esta situación. Si quieres ver la versión anterior haz clic AQUÍ.

A la hora de crear este blog, y dado que se encuentra en Blogger, la dirección original era www.pablotilli.blogspot.com, pero mi idea desde un principio fue utilizar mi propio dominio (www.pablotilli.com.ar), por lo cual tuve que pasar por varias etapas para poder lograrlo y es por esto que me decidí a escribir como hacerlo, para que ustedes también puedan asociar el suyo.

En mi caso, el dominio pablotilli.com.ar ya lo tenía registrado, pero estaba asociado con el hosting de Xmundo (servicio que recomiendo, pues en mi caso en particular, nunca tuve un problema). Asumiendo que nuestro blog ya esta creado, la idea es hacer que nuestro dominio apunte a Blogger, es por esto que el primer paso seria ingresar a NIC.ar y entrar en la zona de "trámites vía web".


Dentro de los trámites que nos permiten realizar vía web, se encuentra el que nos va a interesar si el dominio ya lo tenemos registrado: "Modificaciones de delegaciones".


Una vez dentro de la página de "Modificación de datos de delegación de un dominio" ingresamos el e-mail del responsable, el domino que queremos modificar y pulsamos "continuar".


Aquí surge un inconveniente, ya que Blogger nos pide crear un registro CNAME apuntando a ellos (ghs.google.com), pero NIC.ar no permite la creación de este tipo de registros, sólo podemos especificar los DNS de nuestro servidor.


Cuando me encontré con esta situación me puse a investigar un poco y finalmente la solución fue usar el servicio de DNS estático de CDmon. CDmon nos permite (entre otros muchos servicios) crear registros CNAME a través de su servicio gratuito. Para lograr esto, ante todo debemos registrarnos en CDMon (lo cual no tiene ninguna complejidad).

Para poder usar el servicio de CDMon volvemos a donde nos habíamos quedado en NIC.ar y donde nos permite especificar los DNS de nuestro servidor, escribimos los de CDmon y pulsamos "continuar".

ns1.cdmon.net -> 212.36.74.129

ns2.cdmon.net -> 212.36.75.129

ns3.cdmon.net -> 213.186.58.203

Ahora debemos especificar el proveedor de servicios de Internet (ISP); para esto escribimos 10dencehispahard SL, y pulsamos "buscar".

elegimos el ISP de la lista de resultados (Al dia de hoy <08-01-2008> hay dos resultados y debemos elegir el primero, el cual no termina con punto) y pulsamos "continuar".


En este punto debemos especificar el contacto técnico de CDmon: Josep Colominas. Al igual que en el caso anterior, escribimos el nombre y pulsamos "buscar".


Elegimos Josep Colominas en la lista de resultados y pulsamos "continuar".


Ahora nos mostraran una página de confirmación con todos los datos que ingresamos. Revisamos que todo este bien, y pulsamos "confirmar". Por ultimo, nos aparecerá una página avisando que NIC.ar nos ha enviado un e-mail que deberemos reenviar tal cual como lo recibimos. Una vez reenviado el e-mail, deberemos esperar aprox. 48 horas hábiles para que finalice el tramite (Si el dominio lo estamos registrando y no modificando como en mi caso, te puede tardar incluso mas de 7 días ...paciencia!!!).

Una vez que NIC.ar nos envié el e-mail avisando que el tramite ha finalizado, entramos a nuestro panel de control de CDMon, e ingresamos en la opción "DNS estático".

Dentro de la página de "Panel de control del servidor DNS estático" debemos añadir nuestro dominio como se muestra en la siguiente imagen:


El siguiente paso es la primera diferencia que hay con el metodo anterior. Al añadir nuestro dominio en CDmon, en este punto creavamos un registro CNAME, pero ahora, de las varias opciones que hay, nos interesa la que se llama "Redirección URL".


Aquí debemos especificar que el dominio principal (tu-dominio.com.ar) sea redireccionado a www.tu-dominio.com.ar. Para esto sólo debemos escribir la URL destino (www.pablotilli.com.ar en mi caso) y pulsar "guardar".


Si todo fue bien, nuestro Panel de control debería quedar como en la siguiente imagen:


Como se puede ver en la parte superior de la captura de pantalla, CDmon nos indica el tiempo estimado para que el cambio sea efectivo (en este caso faltaban 24 minutos aprox.). Hasta aquí especificamos que la dirección: "http://www.tu-dominio.com.ar" apunte a Blogger, pero aún nos falta hacer lo mismo con la dirección "http://tu-dominio.com.ar" (sin www). Para esto pulsamos sobre el botón de "Crear un registro A (un subdominio)".


Ahora nos queda por escribir el nombre del subdomino, para lo cual solo debemos escribir www, y pulsamos "guardar":

Al pulsar "guardar" volveremos a nuestro Panel de control donde veremos que se ha listado el subdominio que acabamos de agregar, pero aun falta configurarlo, por lo que dice "Por determinar". Para configurarlo pulsamos el botón de "Editar la configuración del registro" como se ve en la imagen a continuación:


De la opciones que nos aparecen para configurar nuestro subdominio, elegimos "Registro CNAME".


En esta página debemos escribir como dominio: ghs.google.com, y pulsamos "guardar".


Tras pulsar "guardar", ya esta todo listo; sólo debemos habilitar el dominio personalizado en Blogger, y esperar el tiempo que CDmon nos informa que falta para que los cambios sean efectivos. El panel de control de CDmon, finalmente debería quedarte asi:



Para habilitar el dominio personalizado en Blogger, accedemos a nuestro Blog con nuestra cuenta, y una vez dentro, elegimos la opción "Configuración".

vamos a la opciones de "Publicación",

hacemos click en la opción de "cambiar por Dominio personalizado",

escribimos nuestro dominio .com.ar (con www, ya que este es el nuevo requerimiento de Blogger para los dominios personalizados, y es el motivo de la actualizacion de este Post),


y por ultimo simplemente escribimos el texto de verificación, pulsamos "guardar configuración" y tarea cumplida.


Si algo no te quedo claro, no dudes en preguntar. Si quieres hacer alguna critica o sugerencia, espero tu comentario también.

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!