viernes, 5 de febrero de 2010

Crear una agenda en Python - Segunda parte (funcionamiento)

En este post vamos a seguir en la encrucijada de crear una agenda personal en lenguaje Python, utilizando para ello la interfaz gráfica que nos proporciona el framework wxPython. Puesto que ya hemos creado el diseño, ahora vamos a diseñar el funcionamiento, completamente independientemente del diseño. Esta es una buena opción, ya que si algún día quisiéramos cambiar la interfaz, en principio, no debería tener nada que ver con cómo se guardan datos y se recuperan.

Bien, como de lo que se trata es de crear una agenda personal, lo primero que hay que ver, es ¿qué es una agenda para mí? ¿qué debe hacer como funcionamiento mínimo (tampoco es cuestión de desarrollar aquí un Mozilla Sunbird)? La respuesta a esta pregunta se resume a continuación:

1) Una agenda consta de entradas, que se identifican de manera unívoca.

2) A cada entrada yo le podré agregar un texto, de manera que a partir del identificador de entrada (el cual es unívoco) yo pueda acceder a la información que he introducido como texto.

3) Se podrán crear tantas entradas como se deseen, con su información correspondiente, pero no podrá haber entradas repetidas. Cada entrada tiene su propia información (texto). Las entradas no comparten información.

4) Se pueden eliminar entradas (y por ende, su información asociada), crear entradas y modificar entradas existentes (solo se podrá modificar el texto asociado, nunca el identificador de entrada).

5) Por norma, el identificador de entrada será una fecha, ya que estamos hablando de una agenda, y normalmente las agendas se dividen en meses y días dentro del mes. ¡¡¡Nuestra agenda incluye hasta años!!!

Una vez tenemos claro el funcionamiento de la agenda, hay que ver como se va a gestionar esa información, esto es, donde se va a guardar. Como no es cuestión de utilizar bases de datos, (que podría hacerse en MySQL, PostGreSQL, ó SQLLite, por ejemplo) lo siguiente más sencillo es guardarlo en un fichero de texto. Y aquí tenemos mucho ganado, ya que una de las cosas en las que (hay muchas) Python es extremadamente bueno es en la manipulación de documentos XML. Por tanto guardaremos la información en un fichero XML, con una estructura muy sencilla, tal como esta:



<agenda_personal>
<codigo_referencia fecha="2010-02-01">
<texto>
Comer en casa de Paco
</texto>
</codigo_referencia>
<codigo_referencia fecha="2010-02-02">
<texto>
Me voy a las 16:00 a casa de Ángel.
</texto>
</codigo_referencia>
<codigo_referencia fecha="2010-02-03">
<texto>
Ir al cine con María
</texto>
</codigo_referencia>
</agenda_personal>



Se trata de un documento sencillo, comprensible por todos. Podríamos haber incluido en nuestra agenda el contemplar el registro de rangos de horas, pero tampoco es cuestión de complicar, aunque eso sería una mejora considerable.

Ya sabemos cómo queremos que funcione nuestra agenda, y cómo se guardarán los datos. ¿Qué necesitamos más? Pues diseñar la solución. Como Python soporta la orientación a objetos (mas que soportarla, es que es un lenguaje totalmente orientado a objetos), utilizaremos este paradigma para el diseño de la solución.

Si estamos hablando de cierta "información", como una agenda, que es un conjunto ordenado (está estructurado en fechas de entrada, con información asociada) de datos, y dichos datos deben de ser manipulados de algún modo, y de manera controlada, evidentemente nos surge el diseño de una clase tipo agenda, que contenga la estructura de información deseada, y unos métodos, para poder acceder a ella, de manera exclusiva, esto es, solo los métodos apropiados de la clase podrán acceder a la información que contiene. Como aquí se presupone que todos sabemos Programación Orientada a Objetos, no digo más al respecto.

A nivel de estructura he utilizado el módulo xml.dom.minidom Python para manipular el documento XML en memoria. El código siguiente lo he guardado en un fichero, denominado agenda_modelo.py:


# -*- coding: cp1252 -*-

'''
Módulo: agenda_modelo.py
Autor: Ángel Luis García García
Fecha: 03-02-2010
Descripción: Modelo de Angenda Personal.
'''

import os
import datetime
from xml.dom import minidom

class gestion_agenda(object):
def __init__(self,nombre_fichero_agenda):
self.nom_fich = nombre_fichero_agenda + ".xml"
try:
# Cargamos agenda desde fichero.
self.arbol_dom = minidom.parse(os.path.realpath(self.nom_fich))
self.doc_root = self.arbol_dom.documentElement

except:
# No hay fichero agenda. Creamos estructura DOM.
# Creamos el objeto DOM.
implementacion_DOM = minidom.getDOMImplementation()

# Creamos el objeto Document.
self.arbol_dom = implementacion_DOM.createDocument(None,"Agenda_Personal",None)
self.doc_root = self.arbol_dom.documentElement

def __insertar_dia(self, id_ref, texto):
nodo = self.arbol_dom.createElement("codigo_referencia")

# Incluimos el atributo al nodo.
nodo.setAttribute("fecha",id_ref)

# Creamos elemento.
elemento = self.arbol_dom.createElement("texto")

# Inluimos la información.
elemento.appendChild(self.arbol_dom.createTextNode(texto))

# Añadimos al nodo el elemento.
nodo.appendChild(elemento)

# Añadimos el nodo a la raíz del documento.
self.doc_root.appendChild(nodo)

def modificar_dia(self, fecha, texto):
if len(texto) == 0:
self.__eliminar_dia(fecha)
return

nodo = self.__obtener_dia(fecha)
if (nodo == None):
# Si el nodo no existe, , se crea con la información.
self.__insertar_dia(fecha,texto)
else:
# Si el nodo existe, se actualiza la información.
x = nodo.getElementsByTagName("texto")[0]
x.childNodes[0].nodeValue = texto

def __obtener_texto_dia(self,nodo):
ret = ''
if nodo != None:
x = nodo.getElementsByTagName("texto")[0]
ret = x.childNodes[0].nodeValue
return ret

def texto_dia(self, fecha):
aux = self.__obtener_texto_dia(self.__obtener_dia(fecha))
return aux

def __obtener_dia(self, fecha):
'''
Si no encuentra el día, devuelve None. De lo contrario, devuelve
el nodo del día.
'''
ret = None
nodos = self.arbol_dom.childNodes
lista = nodos[0].getElementsByTagName("codigo_referencia")
for nodo in lista:
if nodo.hasAttribute("fecha"):
id_fecha = nodo.getAttribute("fecha")
if id_fecha == fecha:
ret = nodo
break
return ret

def __eliminar_dia(self, fecha):
'''
Si encuentra un nodo con el id fecha, lo elimina.
'''
nodos = self.arbol_dom.childNodes
lista = nodos[0].getElementsByTagName("codigo_referencia")
for nodo in lista:
if nodo.hasAttribute("fecha"):
id_fecha = nodo.getAttribute("fecha")
if id_fecha == fecha:
self.doc_root.removeChild(self.doc_root.childNodes[0])
break

def generar_id(self, fecha = None):
if fecha == None:
# Si no se pasa fecha, se crea ID con fecha actual.
fecha = str(datetime.datetime.now())[0:10]
else:
fecha = str(fecha)[0:10]

return fecha

def mostrar_arbol_dom(self):
print self.arbol_dom.toprettyxml()

def guardar_agenda(self):
# Abrimos fichero en donde guardar el documento XML.
fichero = open(os.path.realpath(self.nom_fich), "w")

# Escribimos con writexml en la cabecera la codificación 'iso-8859-1', para
# no tener problemas con tildes y demás. Aparecerá así:
# <?xml version="1.0" encoding="iso-8859-1"?>
self.arbol_dom.writexml(fichero, encoding='iso-8859-1')

# Finalmente cerramos el fichero.
fichero.close()

Esta es la clase "gestion_agenda", con los siguientes métodos:

modificar_dia(dia,texto): Inserta, actualiza o elimina un registro de texto.
Si el texto es cadena vacía, se elimina el día (nodo).
Si el día no existe, se crea el día (nodo).
Si el día existe, se modifica el día (nodo).

guardar_agenda: Guarda la agenda en fichero, con estructura XML.

texto_dia(fecha): Devuelve la cadena de texto correspondiente a fecha.

generar_id(fecha): Genera ID fecha a partir de cadena fecha.

mostrar_arbol_dom: Muestra por consola el árbol DOM del documento XML,
que actualmente está en memoria.

Cosas de interés para comentar:

1) Para guardar la estructura de árbol DOM en el fichero XML he utilizado el método writexml del siguiente modo: self.arbol_dom.writexml(fichero, encoding='iso-8859-1')
Esto lo he hecho así para poder incluir encoding="iso-8859-1" en la cabecera del fichero, de modo que en la primera línea del archivo XML tendríamos:
<?xml version="1.0" encoding="iso-8859-1"?>

La codificación iso-8859-1 es la Latin-1, para poder escribir caracteres extendidos (ñ, tildes) en el documento XML.

2) Para poder acceder al fichero en donde se guardará el documento XML he utilizado os.path.realpath(self.nom_fich), donde nom_fich es el nombre del fichero. De esta manera me ahorro el estar viendo temas de rutas relativas y absolutas. Como sé que el fichero XML está en el mismo sitio en donde tengo la aplicación de la agenta, lo único que hago es calcular la ruta absoluta a partir de donde está el fichero.

3) En el __init__ de la clase, como se puede observar, lo único que hago es que si no se encuentra el fichero XML, creo directamente una estructura DOM para crear el documento XML.

4) En el método modificar_dia es donde se "cuece" todo. Aquí se presenta el "modelo de negocio", mas menos. Esto es, no puede haber entradas con texto vacío, y si las hay (podía haberlas si hay una entrada con texto y se modifica eliminando dicho texto), se eliminan. Se puede modificar el texto de una entrada e insertar entradas nuevas.

Para probar si este código funciona, lo que podemos hacer es irnos a IDLE (a una ventana nueva, pegamos el código de arriba), crear un objeto agenda, y probar los métodos, tal como así:

(...lo que hay arriba...)

agenda = gestion_agenda('agenda')
agenda.modificar_dia('2010-02-01', 'Comer en casa de Paco')
agenda.modificar_dia('2010-02-02', 'Me voy a las 16:00 a casa de Ángel.')
agenda.mostrar_arbol_dom()
agenda.guardar_agenda()

Si vamos al directorio en donde tenemos el fichero .py vemos que hemos creado el fichero agenda.XML, con los datos que le hemos incluido.

Explicar el código no tiene mucho sentido, ya que está lo suficientemente comentado. Además, la sintaxis de Python tiene algo buenísimo, y es que es muy fácil de leer y entender. Lo único "difícil" podría ser la manipulación de XML. En breve escribiré un post sobre minidom y la manera de manipular XML.

Resumiendo, ahora tenemos, por un lado, el diseño gráfico de nuestra aplicación, y por otro el modelo de negocio, cómo funcionará nuestra aplicación. ¿Qué nos falta? Pues unir estos dos elementos, de manera que a partir de manipular el objeto que hemos diseñado con wxGlade (nuestra ventana) se puedan generar eventos tal como incluir texto en la agenda, guardar la agenda, y demás. Hay que construir un sistema para controlar los eventos que se lanzarán cuando interactuemos con nuestra aplicación a partir del diseño gráfico. Así, por ejemplo, si cerramos nuestra ventana, hay que lanzar un evento para decirle que tiene que ejecutar el método guardar_agenda().

La creación de manejadores de eventos, y su utilización lo veremos en un siguiente post.

No hay comentarios:

Publicar un comentario