Bueno, pues para ir practicando con python, en entorno grafico GTK, y con bases de datos MySQL, he hecho estos widgets heredados de widgets GTK, para poder manejar tablas MySQL, no esta demasiado comentado, si a alguien le interesa el tema puede consultarme lo que quiera.
Estoy preparando un programita ejemplo de uso de estos widgets, lo enviare proximamente.
Tened en cuenta que os hace falta ademas del python, el motor de BD MySQL, GTK2 y las librerias de python para MySQL, mysqldb y para gtk pygtk.
Ala, saludos : Gaby
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
import MySQLdb
class Database:
def __init__(self,database,usuario=None,password=None):
self.database_nombre=database
self.conectada=False
if usuario!=None:
self.mysquser=usuario
else:
self.mysquser='usuario' #podemos poner el valor en la clase si va a ser siempre el mismo
if password!=None:
self.mysqpasw=password
else:
self.mysqpasw='********' #podemos poner el password por defecto, igual que el usuario
self.conexion=None
def conectar(self):
try:
self.conexion=MySQLdb.connect(host='localhost',user=self.mysquser,passwd=self.mysqpasw,db=self.database_nombre)
self.conectada=True
return self.conexion
except:
#error, sacamos dialogo y decimos que hagan configuracion
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error conectando a base de datos\n Compruebe configuracion\n o MySQL")
md.run()
md.destroy()
return False
def set_dbdatos(self,usuario=None,password=None):
if usuario!=None:
self.mysquser=usuario
if password!=None:
self.mysqpasw=password
class Tabla:
def __init__(self,tabla,basedatos):
# los errores en valores de datos en el sql, no da error da warning, lo
# pasamos a error para poder trabajar con try: except:
import warnings
warnings.simplefilter("error")
self.database=basedatos
self.abierta=False
self.tabla=tabla
self.datos_tabla=[]
self.widgets=[]
self.descripcion_tabla=[]
self.nombre_campos=[]
self.tipo_campos=[]
self.long_campos=[]
self.num_campos=0
self.num_lineas=0
self.tablas_rel=[]
self.campos_rel=[]
self.estado='consultar'
self.linea_actual=[]
self.indexado=False
self.indice=''
self.abrir()
def abrir(self):
#abre la tabla
res=self.abre_tabla()
if res:
self.datos_tabla=self.cursor.fetchall()
self.num_lineas=len(self.datos_tabla)
self.linea_actual=self.datos_tabla[0]
self.linea_pos=0
#num_campos lleva el numero de campos
self.num_campos=len(self.cursor.description)
for i in range(self.num_campos):
self.nombre_campos.append(self.cursor.description[i][0])
self.tipo_campos.append(self.cursor.description[i][1])
self.long_campos.append(self.cursor.description[i][3])
#cerramos la tabla y la base de datos, y trabajamos con los
#datos en la variable datos_tabla
self.cierra_tabla()
return res
def abre_tabla(self):
#conectamos la base de datos
self.conn=self.database.conectar()
self.cursor=self.conn.cursor()
if self.database.conectada==True: #True si estamos conectados a la base de datos
try:
sql='select * from '+self.tabla
if self.indexado:
sql=sql+' order by '+self.indice
self.cursor.execute(sql)
self.abierta=True
result=True
except:
#error, sacamos dialogo y decimos que hagan configuracion
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error abriendo tabla\n Compruebe configuracion\n o MySQL")
md.run()
md.destroy()
result=False
else:
#la base de datos no esta conectada
#error, sacamos dialogo y decimos que hagan configuracion
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Base de datos no abierta\n Compruebe configuracion\n o MySQL")
md.run()
md.destroy()
result=False
return result
def cierra_tabla(self):
self.cursor.close()
self.conn.close()
def set_indice(self,indexado,*indice):
if indexado:
self.indexado=True
self.indice=indice[0]
self.abrir()
else:
self.indexado=False
def set_tabla(self,tabla):
self.tabla=tabla
self.abrir()
def adelante(self,widget):
# una fila adelante en la tabla.
if self.linea_pos<self.num_lineas-1:
self.linea_pos+=1
self.linea_actual=self.datos_tabla[self.linea_pos]
result=True
if self.linea_pos==self.num_lineas-1:
result=False
self.actualizar()
else:
#hemos alcanzado final tabla
result=False
return result
def atras(self,widget):
#una fila hacia atras, si es la primera, no cambia
if self.linea_pos>0:
self.linea_pos-=1
self.linea_actual=self.datos_tabla[self.linea_pos]
result=True
if self.linea_pos==0:
result=False
self.actualizar()
else:
#principio de tabla
result=False
return result
def primero(self,widget):
self.linea_pos=0
self.linea_actual=self.datos_tabla[self.linea_pos]
self.actualizar()
return True
def ultimo(self,widget):
self.linea_pos=self.num_lineas-1
self.linea_actual=self.datos_tabla[self.linea_pos]
self.actualizar()
return True
def aplica_edicion(self):
#aqui salvamos los datos editados
#primero seleccionamos la fila de la tabla a cambiar
#los datos sin editar estan en la variable linea_actual[]
#hacemos el select con todos los campos, pues no sabemos
#si hay dos lineas con campos iguales.
#primero abrimos tabla
#pero antes guardamos el puntero que llevabamos
linea_antes=self.linea_pos
self.abre_tabla()
datos_antes=''
for i in range(self.num_campos):
campo=self.nombre_campos[i]
dato=str(self.linea_actual[i])
if dato!='' and dato!='None':
datos_antes=datos_antes+campo+' = "'+dato+'" AND '
datos_antes=str(datos_antes[0:len(datos_antes)-4])
sql='select * from '+self.tabla+' where '+datos_antes
try:
self.cursor.execute(sql)
except:
#error, el registro a modificar tenia valores inconsistentes
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error en registro\n Debe arreglarlo desde\n Administrador MySQL\n")
md.run()
md.destroy()
return False
#una vez seleccionada la linea a modificar, comprobamos que es una
# y solo una.
if self.cursor.rowcount != 1:
#error, sacamos dialogo y decimos que comprueben datos
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error al grabar datos\n Mas de un registro cambiado\n ")
md.run()
md.destroy()
return False
else:
#correcto, anotamos los nuevos valores
sql='update '+self.tabla+' set '
for i in range(self.num_campos):
campo=self.nombre_campos[i]
dato=self.widgets[i].get_text()
if dato!='':
if i<self.num_campos-1:
sql=sql+campo+' = "'+dato+'" , '
else:
sql=sql+campo+' = "'+dato+'"'
else:
if i<self.num_campos-1:
sql=sql+campo+' = NULL, '
else:
sql=sql+campo+' = NULL'
sql=sql+' where '+datos_antes
try:
self.cursor.execute(sql)
#abrir abre la tabla, carga las datos en variables y cierra tabla
self.abrir()
#dejamos en pantalla el registro modificado
self.linea_pos=linea_antes
#quita el estado de edicion de los widgets
self.estado_consulta()
result=True
except:
#error, sacamos dialogo y decimos que comprueben datos
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error al grabar datos\n Compruebe datos \n o Cancele")
md.run()
md.destroy()
result=False
return result
def aplica_alta(self):
#anota nuevo registro en la tabla
#aqui salvamos los datos anotados
#primero abrimos tabla
self.abre_tabla()
#correcto, anotamos los nuevos valores
sql='insert into '+self.tabla+' values ( '
vacio=True
for i in range(self.num_campos):
dato=self.widgets[i].get_text()
if dato != '':
vacio=False
if i<self.num_campos-1:
sql=sql+'"'+dato+'" , '
else:
sql=sql+'"'+dato+'" )'
else:
if i<self.num_campos-1:
sql=sql+' NULL, '
else:
sql=sql+' NULL )'
if vacio:
#error, sacamos dialogo y decimos que comprueben datos
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"No se puede guardar un\n Registro vacio\n")
md.run()
md.destroy()
return False
try:
self.cursor.execute(sql)
result=True
self.abrir()
self.estado_consulta()
except:
#error, sacamos dialogo y decimos que comprueben datos
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error al grabar datos\n Compruebe datos \n o Cancele")
md.run()
md.destroy()
result=False
return result
def borrar(self):
#primero seleccionamos la fila de la tabla a borrar
#los datos estan en la variable linea_actual[]
#hacemos el select con todos los campos, pues no sabemos
#si hay dos lineas con campos iguales.
#primero abrimos tabla
#pero antes guardamos el puntero que llevabamos
linea_antes=self.linea_pos
self.abre_tabla()
datos_antes=''
for i in range(self.num_campos):
campo=self.nombre_campos[i]
dato=str(self.linea_actual[i])
if dato!='' and dato!='None':
datos_antes=datos_antes+campo+' = "'+dato+'" AND '
datos_antes=str(datos_antes[0:len(datos_antes)-4])
sql='select * from '+self.tabla+' where '+datos_antes
try:
self.cursor.execute(sql)
except:
#error, el registro a modificar tenia valores inconsistentes
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error en registro\n Debe arreglarlo desde\n Administrador MySQL\n")
md.run()
md.destroy()
return False
#una vez seleccionada la linea a borrar, comprobamos que es una
# y solo una.
if self.cursor.rowcount != 1:
#error, sacamos dialogo y decimos que comprueben datos
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error al borrar registro\n Mas de un registro seleccionado\n ")
md.run()
md.destroy()
return False
else:
#correcto, borramos la linea
sql='delete from '+self.tabla+' where '+datos_antes
try:
self.cursor.execute(sql)
#abrir abre la tabla, carga las datos en variables y cierra tabla
self.abrir()
#dejamos en pantalla el registro modificado
self.linea_pos=linea_antes
#quita el estado de edicion de los widgets
self.estado_consulta()
result=True
except:
#error, sacamos dialogo y decimos que comprueben datos
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error al borrar registro\n Compruebe datos \n o Cancele")
md.run()
md.destroy()
result=False
return result
def cancelar(self):
puntero=self.linea_pos
self.abrir()
self.linea_pos=puntero
self.linea_actual=self.datos_tabla[self.linea_pos]
self.actualizar()
self.estado_consulta()
return
def buscar(self,campo,valor):
for n in range(self.num_campos):
if campo==self.nombre_campos[n]:
campo_index= n
encontrado=False
for n in range(len(self.datos_tabla)):
if self.datos_tabla[n][campo_index]==valor:
self.linea_actual=self.datos_tabla[n]
encontrado=True
self.actualizar()
break
return encontrado
def actualizar(self):
#isinstance(obj, int)
for w in self.widgets:
w.actualizar_widget()
self.actualizar_datos_relacionada()
return True
def estado_consulta(self):
#pone los widgets en estado de consulta
self.estado='consultar'
for w in self.widgets:
w.set_editable(False)
def estado_editar(self):
#pone los widgets en estado de edicion
self.estado='editar'
for w in self.widgets:
w.set_editable(True)
def estado_alta(self):
#pone los widgets en alta, en blanco
self.estado='alta'
for w in self.widgets:
w.set_text('')
w.set_editable(True)
def widget_a_tabla(self,widget,campo):
self.widgets.append(widget)
result=-1
for n in range(self.num_campos):
if campo==self.nombre_campos[n]:
result= n
return result
def relacionar(self,campoprop,tablarel,camporel):
#relaciona otra tabla con esta, recibe, campo propio
#tabla esclava a relacionar, y campo de la tabla a relacionar
result=-1
for n in range(self.num_campos):
if campoprop==self.nombre_campos[n]:
result= n
self.tablas_rel.append(tablarel)
self.campos_rel.append(n)
#envia a la tabla peticionaria, la identidad de esta tabla
#y el campo de la peticionaria
tablarel.relacionada(self,camporel)
return result
def relacionada(self,trelacion,camporel):
self.camporel=camporel
self.tablarelacion=trelacion
def actualizar_datos_relacionada(self):
for t in range(len(self.tablas_rel)):
dato=self.linea_actual[self.campos_rel[t]]
tabla=self.tablas_rel[t]
tabla.actualizar_tabla(dato)
return True
def actualizar_tabla(self,dato):
self.buscar(self.camporel,dato)
class DBLabel(gtk.Label):
#un label enlazado a una tabla y un campo de la misma
def __init__(self,tabla,campo=None,orden=None,titular=False):
#si no nos dan el campo, y se da el orden del campo,
#campo=None y orden = n, posicion en la tabla de la columna
#si titular=True, anteponemos el nombre del campo al label
gtk.Label.__init__(self)
self.tabla=tabla
self.titular=titular
if campo==None:
self.campo=self.tabla.nombre_campos[orden]
else:
self.campo=campo
res=self.campo_index=tabla.widget_a_tabla(self,self.campo)
if res==-1:
#error, campo de tabla no encontrado
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error, campo de tabla \n No existente \n")
md.run()
md.destroy()
self.titulo=self.campo+' : '
self.show()
self.actualizar_widget()
def set_titulo(self,titulo):
#pone el nombre que deseemos en titulo, en lugar del nombre del campo
self.titulo=titulo+' : '
self.titular=True
def actualizar_widget(self):
texto=self.tabla.linea_actual[self.campo_index]
if self.titular:
texto=self.titulo+str(texto)
self.set_text(str(texto))
class DBEntry(gtk.HBox):
#un label enlazado a una tabla y un campo de la misma
def __init__(self,tabla,campo=None,orden=None,titular=False):
#si no nos dan el campo, y se da el orden del campo,
#campo=None y orden = n, posicion en la tabla de la columna
#si titular=True, anteponemos el nombre del campo al label
gtk.HBox.__init__(self)
self.set_size_request(500,40)
self.set_homogeneous(False)
self.entry=gtk.Entry()
self.entry.set_editable(False)
self.label=gtk.Label()
self.pack_start(self.label,False,False,False)
self.pack_end(self.entry,False,False,False)
self.entry.show()
self.label.show()
self.show()
self.tabla=tabla
self.titular=titular
if campo==None:
self.campo=self.tabla.nombre_campos[orden]
else:
self.campo=campo
#campo_index lleva el numero de campo en la tabla
res=self.campo_index=tabla.widget_a_tabla(self,self.campo)
if res==-1:
#error, campo de tabla no encontrado
md=gtk.MessageDialog(None,gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,"Error, campo de tabla \n No existente \n")
md.run()
md.destroy()
self.titulo=self.campo+' : '
#poner anchura adaptada a la del campo
ancho=tabla.long_campos[self.campo_index]
self.entry.set_max_length(ancho)
self.entry.set_width_chars(ancho)
self.actualizar_widget()
def set_titulo(self,titulo):
#pone el nombre que deseemos en titulo, en lugar del nombre del campo
self.titulo=titulo+' : '
self.titular=True
def set_editable(self,estado):
self.entry.set_editable(estado)
def get_text(self):
return self.entry.get_text()
def set_text(self,texto):
self.entry.set_text(texto)
def actualizar_widget(self):
texto=self.tabla.linea_actual[self.campo_index]
if self.titular:
self.label.set_text(self.titulo+' : ')
self.label.set_alignment(0,0.5)
self.entry.set_text(str(texto))
class Navegador(gtk.HButtonBox):
def __init__(self,tabla,main=None):
#conjunto de botones para navegar y actuar sobre la tabla
gtk.HButtonBox.__init__(self)
self.main=main
self.tabla=tabla
self.estado='consultar'
self.set_homogeneous(False)
# METEMOS LOS BOTONES
self.principio=gtk.Button('<<')
self.add(self.principio)
self.principio.connect_object("clicked", self.movimiento, self.principio)
self.atras=gtk.Button('<')
self.add(self.atras)
self.atras.connect_object("clicked", self.movimiento, self.atras)
self.delante=gtk.Button('>')
self.add(self.delante)
self.delante.connect_object("clicked", self.movimiento, self.delante)
self.fin=gtk.Button('>>')
self.add(self.fin)
self.fin.connect_object("clicked", self.movimiento, self.fin)
self.editar=gtk.Button('Editar')
self.add(self.editar)
self.editar.connect_object("clicked", self.control, self.editar)
self.borrar=gtk.Button('Borrar')
self.add(self.borrar)
self.borrar.connect_object("clicked", self.control, self.borrar)
self.alta=gtk.Button('Alta')
self.add(self.alta)
self.alta.connect_object("clicked", self.control, self.alta)
self.aplicar=gtk.Button('Aplicar')
self.add(self.aplicar)
self.aplicar.connect_object("clicked", self.control, self.aplicar)
self.cancelar=gtk.Button('Cancelar')
self.add(self.cancelar)
self.cancelar.connect_object("clicked", self.control, self.cancelar)
if main!=None:
#crea un toolbar en la ventana que contiene al navegador,
#donde representamos el numero de registro visualizado.
self.lestado=gtk.Label('inicio')
labelitem=gtk.ToolItem()
labelitem.add(self.lestado)
self.toolb=gtk.Toolbar()
self.toolb.insert(labelitem,0)
main.vbox.pack_start(self.toolb,False,True)
self.lestado.show()
labelitem.show()
self.toolb.show()
self.atras.show()
self.delante.show()
self.principio.show()
self.fin.show()
self.editar.show()
self.borrar.show()
self.alta.show()
self.show()
self.actualizar_widget()
def actualizar_widget(self):
if self.tabla.linea_pos==0:
#primera linea
inicio=True
fin=False
elif self.tabla.linea_pos==len(self.tabla.datos_tabla)-1:
#ultima linea
fin=True
inicio=False
else:
inicio=False
fin=False
if self.tabla.num_lineas==1:
#solo hay un registro, ni palante ni patras
inicio=True
fin=True
# oculta los botones que no estan operativos
if self.estado != 'consultar':
self.delante.hide()
self.fin.hide()
self.atras.hide()
self.principio.hide()
self.editar.hide()
self.alta.hide()
self.borrar.hide()
self.aplicar.show()
self.cancelar.show()
self.cancelar.grab_focus()
if self.main!=None:
self.lestado.set_text('Record : '+str(self.tabla.linea_pos)+' estado : '+self.estado)
else:
self.delante.show()
self.fin.show()
self.atras.show()
self.principio.show()
self.editar.show()
self.alta.show()
self.borrar.show()
self.aplicar.hide()
self.cancelar.hide()
if self.main!=None:
self.lestado.set_text('Record : '+str(self.tabla.linea_pos))
if inicio:
self.atras.hide()
self.principio.hide()
# y anota en el label el numero de record mostrado
if self.main!=None:
self.lestado.set_text('Record : '+str(self.tabla.linea_pos)+' Principio de fichero')
if fin:
self.delante.hide()
self.fin.hide()
if self.main!=None:
self.lestado.set_text('Record : '+str(self.tabla.linea_pos)+' Fin de fichero')
def movimiento(self,widget):
if widget==self.delante:
self.tabla.adelante(widget)
elif widget==self.atras:
self.tabla.atras(widget)
elif widget==self.principio:
self.tabla.primero(widget)
elif widget==self.fin:
self.tabla.ultimo(widget)
self.actualizar_widget()
def control(self,widget):
res=True
if widget==self.editar:
self.estado='editar'
self.tabla.estado_editar()
elif widget==self.alta:
self.estado='alta'
self.tabla.estado_alta()
elif widget==self.borrar:
self.tabla.estado='borrar'
self.estado='borrar'
elif widget==self.aplicar:
if self.estado=='editar':
#actualiza los datos en la tabla
res=self.tabla.aplica_edicion()
if res:
self.estado='consultar'
elif self.estado=='alta':
res=self.tabla.aplica_alta()
if res:
self.estado='consultar'
else:
self.tabla.borrar()
self.estado='consultar'
elif widget==self.cancelar:
self.tabla.cancelar()
self.estado='consultar'
if res:
self.actualizar_widget()