# -*- coding: utf-8 -*-
"""
    Utilidades para trabajar con PostgreSQL.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    @copyright: (C) 2011 by J. Gaspar Mora Navarro
    @contact: upvdelprop@gmail.com
    @author: J. Gaspar Mora navarro.
    @organization: U.P. Valencia. Dep Ing Cart. Geod. y Fotogrametria
    @version: 1
    @summary: Utilidades para trabajar con PostgreSQL.
"""
__docformat__ = "epytext"

import psycopg2
import psycopg2.extensions
import os

import sys
#sys.path.append("C:\eclipse\plugins\org.python.pydev.debug_2.3.0.2011121518\pysrc")
#from pydevd import *
import pyGenGas

class ConectaPg(object):
    """
    Clase que intenta realizar una conexion a posgres:
    Configura psycopg2 para que todo lo que devuelva postgres este en unicode
    """
    def __init__(self,database, user, password,host, port,connection_timeout=None):
        """
        Constructor:

        Llama a __set_conn para intentar la conexionConfigura postgre
        Configura psycopg2 para que todo lo que nos devuelva postgres este en unicode

        @type  database: string
        @param database: El nombre de la base de datos.
        @type  user: string
        @param user: usuario que intenta la conexion
        @type  password: string
        @param password: contrasenya
        @type  host: string
        @param host: IP del ordenador donde este la base de datos.
        """
        #Configura psycopg2 para que todo lo que devuelva postgres este en unicode
        psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
        psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)

        self.database=database
        self.usuario=user
        self.password=password
        self.host=host
        self.port=port
        self.connection_timeout=connection_timeout

        self.__conn=None
        self.__cursor=None
        self.__conectado=False
        self.__error=None
        self.__descripcion_error=""
        if connection_timeout==None:
            cad_conn="dbname=" + database + " user=" + user + " password=" + password + " host=" + host + " port=" + str(port)
        else:
            cad_conn="dbname=" + database + " user=" + user + " password=" + password + " host=" + host + " port=" + port + " connect_timeout=" + str(connection_timeout)
        self.__set_conn(cad_conn)


    def __set_conn(self,cad_conn):
        """
        Intenta conectar con postgress:
            - Si la realiza establece las propiedades a los siguientes valores:
                - conn: la conexion
                - cursor: el cursor de la conexion
                - conectado: True
                - descripcion_error:
                - error: None
            - Si no realiza la conexion establece las propiedades a los siguientes valores:
                - conn: None
                - cursor: None
                - conectado: False
                - descripcion_error: Cadena con la descripcion de postgres
                - error: el error, instancia de Error.
        """
        try:
            self.__conn=psycopg2.connect(cad_conn)
            self.__cursor=self.__conn.cursor()
            self.__conectado=True
            self.__descripcion_error=""
            self.__error=None
        except Exception, e:
            self.__conn=None
            self.__cursor=None
            self.__conectado=False
            self.__error=e
            self.__descripcion_error=e.message
    def __set_usuario(self,usuario):
        self.__usuario=usuario
    def __get_usuario(self):
        return self.__usuario
    def __get_conn(self):
        return self.__conn
    def __get_cursor(self):
        return self.__cursor
    def __get_conectado(self):
        return self.__conectado
    def __get_error(self):
        return self.__error
    def __get_descripcion_error(self):
        return self.__descripcion_error
    def cierraConexion(self):
        """
        Cierra la conexion
        """
        self.__cursor.close()
        self.__conn.close()
        self.__conectado=False

    usuario=property(__get_usuario,__set_usuario,"Numero de usuario. Debe tener una fila en comun.usuarios")
    conn=property(__get_conn,"Solo lectura. La conexion a postgress")
    cursor=property(__get_cursor,"Solo lectura. El cursor de la conexion")
    conectado=property(__get_conectado,"Solo lectura. Es True en si ha realizado la conexion")
    error=property(__get_error,"Solo lectura. El error devuelto por postgres")
    descripcion_error=property(__get_descripcion_error,"Solo lectura. ")

class GeneraExpresionesPsycopg2(object):
    """
    Genera expresiones para realizar selects o inserts
    """
    def generaWhere(self,listaCampos, and_or):
        """
        Genera una cadena con la condición where para una consulta sql.
        Recibe una lista como ["dni",nombre","apellido" ], y devuelve una cadena con:
        " dni=%s and nombre=%s and apellido=%s", en el caso de que and_or sea "and"
        si and_or es "or", entonces devuelve
        " dni=%s or nombre=%s or apellido=%s"

        B{NO INTRODUCE LA PALABRA WHERE EN LA CADENA DEVUELTA.}

        @type listaCampos: lista
        @param listaCampos: Lista con los nombres de los campos que deben aparecer en la condicion where
        @type and_or: string
        @param and_or: puede valer "and" u "or".
        @return: un string con la condición where. Si lista campos no tiene elementos, devuelve ""
        """
        if len(listaCampos)<1:
            return None
        #if listaCampos.
        condWhere= " "
        for campo in listaCampos:
            condWhere=condWhere + campo + "=%s " + " " + and_or +" "
        if and_or=="and":
            condWhere=condWhere[:-4]
        else:
            condWhere=condWhere[:-3]
        return condWhere

    def generaInsertPsycopg2(self,nombreTabla, listaCampos):
        """
        Genera una consulta INSERT para ser usada con psycop2. Es del tipo:
            - "INSERT INTO nombreTabla (campo1, campo2, ..) values (%s,%s,...)"

        @type  nombreTabla: string.
        @param nombreTabla: Nombre de la tabla. Si pertenece a un esquema diferente de public hay que poner "nombreEsquema.nombreTabla".
        @type  listaCampos: lista.
        @param listaCampos: lista con los nombres de los campos de la tabla
        @return: Un string con la consulta
        """
        s="insert into " +nombreTabla + " ("
        for campo in listaCampos:
            s=s+campo + ","
        s=s[:-1]#elimino la ultima coma
        s=s+ ") values ("
        for campo in listaCampos:
            s=s+ "%s,"
        s=s[:-1]#elimino la ultima coma
        s=s+")"
        return s

    def generaUpdatePsycopg2(self,nombreTabla, listaCampos, condicionWhere):
        """
        Genera una consulta UPDATE para ser usada con psycop2. Es del tipo:
            - "UPDATE tabla SET campo1=%s, campo2=%s, ...WHERE id=2356"

        @type  nombreTabla: string.
        @param nombreTabla: Nombre de la tabla. Si pertenece a un esquema diferente de public hay que poner "nombreEsquema.nombreTabla".
        @type  listaCampos: lista.
        @param listaCampos: Lista con los campos de la tabla a actualizar.
        @type condicionWhere: string
        @param condicionWhere: condicion que ha de cumplir el registro para ser actualizado. Ejemplo: id=%s and lugar=%s or lugar=%s.
            Como se ve no hay que poner los valores, hay que poner %s. Esos valores los debe introcir Psycopg2 en cursor.execute(cad,valores)
            Deben estar en la lista valores. Se hace así por seguridad. pysicopg2 escapa los caracteres correctamente.

        @return: Un string con la consulta, solo hay que sustituir %s por los valores correctos.
        """
        s="update " + nombreTabla + " set "
        for campo in listaCampos:
            s=s + campo + "=%s,"
        s=s[:-1]#elimino la ultima coma
        if condicionWhere!=None:
            s=s + " where " + condicionWhere
        return s

    def generaInsertPsycopg2Geom(self,nombreTabla, listaCampos, esMulti, nombreCampoGeom, epsg):
        """
        Genera una consulta INSERT para ser usada con psycop2, conm un campo de geometría. Es del tipo:

            - "insert into h30.fincas (tipo_finca,geom) values (%s,ST_multi(ST_geometryfromtext(%s,25830)))"

        @type  nombreTabla: string
        @param nombreTabla: Nombre de la tabla. ej1:"imagenes" ej2: "esquemaPG.imagenes".
        @type  listaCampos: string
        @param listaCampos: Lista con los nombres de los campos. Ej [nombre, direccion, img]. El último nombre en la lista debe ser el del campo bytea
        @type  esMulti: Booleano
        @param esMulti: Si es multi, se deja como estaba, si no, se convierte a multi.
        @type  nombreCampoGeom: srtring
        @param nombreCampoGeom: nombre del campo de geometría. Debe estar incuído en listaCampos.
        @type epsg: string
        @param epsg: codigo EPSG de la tabla donde se insertará la geometria
        @return: la expresión INSERT para ser usada con psycop2.
        """
        s="insert into " +nombreTabla + " ("
        for campo in listaCampos:
            s=s+campo + ","
        s=s[:-1]#elimino la ultima coma
        s=s+ ") values ("
        for campo in listaCampos:
            if nombreCampoGeom==campo:
                if esMulti==False:
                    s=s+ "ST_geometryfromtext(%s," + epsg + "),"
                else:
                    s=s+ "ST_multi(ST_geometryfromtext(%s," + epsg + ")),"
            else:
                s=s+ "%s,"
        s=s[:-1]#elimino la ultima coma
        s=s+")"
        return s

    def generaUpdatePsycopg2Geom(self,nombreTabla, listaCampos, esMulti, nombreCampoGeom, epsg, condicionWhere):
        """
        NO HA SIDO PROBADO

        Genera una consulta UPDATE para ser usada con psycop2, con un campo de geometría. Es del tipo:
            - "UPDATE tabla set tipo_finca=%s, geom=ST_multi(ST_geometryfromtext(%s,25830))"

        @type  nombreTabla: string
        @param nombreTabla: Nombre de la tabla. ej1:"imagenes" ej2: "esquemaPG.imagenes".
        @type  listaCampos: string
        @param listaCampos: Lista con los nombres de los campos. Ej [nombre, direccion, img].
        @type  esMulti: Booleano
        @param esMulti: Si es multi, se deja como estaba, si no, se convierte a multi.
        @type  nombreCampoGeom: srtring
        @param nombreCampoGeom: nombre del campo de geometría. Debe estar incuído en listaCampos.
        @type epsg: string
        @param epsg: codigo EPSG de la tabla donde se insertará la geometria
        @type condicionWhere: string
        @param condicionWhere: condicion que ha de cumplir el registro para ser actualizado. Ejemplo: id=%s and lugar=%s or lugar=%s.
            Como se ve no hay que poner los valores, hay que poner %s. Esos valores los debe introcir pysicopg2 en cursor.execute(cad,valores)
        @return: Un string con la consulta, solo hay que sustituir %s por los valores correctos.
        """

        s="update " + nombreTabla + " set "
        for campo in listaCampos:
            if nombreCampoGeom==campo:
                if esMulti==False:
                    s=s + campo + "=ST_geometryfromtext(%s," + epsg + "),"
                else:
                    s=s+ campo + "=ST_multi(ST_geometryfromtext(%s," + epsg + ")),"
            else:
                s=s + campo + "=%s,"
        s=s[:-1]#elimino la ultima coma
        if condicionWhere!=None:
            s=s + " where " + condicionWhere
        return s

    def generaSelect(self, nombreTabla, listaCampos, condicionWhere=None,orderBy=None,limit=None):
        """
        Genera una expresion select. Lista para ser usada con psycopg2:
            - "SELECT campo1, campo2, .. FROM nombreTabla WHERE campo1=25;"

        @type  nombreTabla: string
        @param nombreTabla: Nombre de la tabla. ej1:"imagenes" ej2: "esquemaPG.imagenes".
        @type  listaCampos: Lista de strings
        @param listaCampos: Lista de strings con los nombres de los campos a recuperar. Ej: ["id", "nombre", "img"]
        @type condicionWhere: string
        @param condicionWhere: condicion que ha de cumplir el registro para ser actualizado. Ejemplo: id=%s and lugar=%s or lugar=%s.
            Como se ve no hay que poner los valores, hay que poner %s. Esos valores los debe introcir Psycopg2 en cursor.execute(cad,valores)
            Deben estar en la lista valores. Se hace así por seguridad. pysicopg2 escapa los caracteres correctamente.
        @return: Un string con la expresion select. Exception si no se ha podido realizar.
        @type orderBy: string
        @param orderBy: Nombre del campo por el cual se quiere ordenar los registros seleccionados
        @type limit: integer
        @param limit: número máximo de registros devueltos.
        """
        if len(listaCampos) < 1:
            raise Exception("Se necesita al menos un campo para realizar la seleccion")

        s="select "
        for campo in listaCampos:
            s=s+campo + ","
        s=s[:-1]#elimino la ?ltima coma
        s= s + " from " + nombreTabla
        if condicionWhere !=None:
            s=s+ " where " + condicionWhere

        if orderBy!=None:
            s= s + " ORDER BY " + orderBy
        return s

        if limit !=None:
            s= s + " LIMIT " + str(limit) + ";"



class Archivos(object):
    """
    Clase con métodos para trabajar con archivos en formato binario.
    Se utiliza para leer un archivo y, con la clase ConsultasPG
    enviarlo a PostgreSQL.

    También se puede usar para escribir a archivo datos binarios
    recibidos de PostgreSQL. Estos datos binarios se pueden recibir
    tambien usando la clase ConsultasPg
    """

    def leeDatBinarios(self, nombreArchivo):
        """
        Lee el archivo completo en binario

        @type  nombreArchivo: string.
        @param nombreArchivo: Nombre del archivo a leer".
        @return: Los datos binarios leidos. Si no se puede leer el archivo, devuelve un IOError
        """
        try:
            f=open(nombreArchivo, "rb")
            try:
                binary = f.read()
                return binary
            finally:
                if f is not None:
                    f.close()
        except IOError, e:
            e.message=unicode("No se ha podido leer el archivo " + nombreArchivo,"utf-8)")
            return e

    def EscribeDatBinarios(self, nombreArchivo, datBinarios):
        """
        Escribe en el archivo los datos binarios. Si el archivo existe, elimina sus datos

        @type  nombreArchivo: string.
        @param nombreArchivo: Nombre del archivo a escribir".
        @return: True si todo va bien. Si no se puede escribir el archivo, devuelve un IOError
        """

        if datBinarios==None:
            return Exception("La variable datBinarios es None.")
        try:
            f=open(nombreArchivo, "wb")
            try:
                f.write(datBinarios)
                return True
            finally:
                if f is not None:
                    f.close()
        except IOError, e:
            e.message=unicode("No se ha podido leer el archivo ","utf-8") + nombreArchivo
            return e

    def copiaArchivo(self, nombreArchOrigen, nombreArchDestino):
        """
        Realiza la copia de un archivo

        @type  nombreArchOrigen: string.
        @param nombreArchOrigen: Nombre del archivo a copiar".
        @type  nombreArchDestino: string.
        @param nombreArchDestino: Nombre del archivo crear".
        @return: True si todo va bien. Si no se puede escribir el archivo, devuelve un Exception
        """
        binary=self.leeDatBinarios(nombreArchOrigen)
        if isinstance(binary,Exception):
            return binary
        r=self.EscribeDatBinarios(nombreArchDestino,binary)
        if isinstance(r,Exception):
            return r
        return True

    def cambiaTamanoImagen(self, imgEntrada,imgSalida,numColSalida):
        """
        Crea una nueva imagen de un tamaño menor. Si el numero de columnas
        numColsalida es mayor o igual que las columnas de la imagen de entrada,
        se hace una copia de la imagen de entrada.

        @requires: la librería PIL (python imagin library)
        @type imgEntrada: string
        @param imgEntrada: Ruta y nombre de la imagen de entrada
        @type imgSalida: string
        @param imgSalida: Ruta y nombre de la imagen de salida
        @type numColSalida: integer
        @param numColSalida: numero de columnas de salida. La función calcula
        proporcionalmente las filas de salida.

        @return: Si todo va bien devuelve None. Si algo ha salido mal, devuelve
        un tipo Exception con el mensaje de error
        """
        try:
            import PIL.Image
            import os
        except Exception, e:
            e.message="No se ha instalado el paquete PIL. Debe copiarse en el directorio /Quantum GIS Lisboa/apps/Python27/Lib/site-packages"
            return e
#        file, ext = os.path.splitext(nombreImg)
        try:
            im = PIL.Image.open(imgEntrada)
        except Exception, e:
            e.message="No se ha podido leer la imagen " + imgEntrada + " No deben haber ni acentos, ni enyes en la ruta"
            return e
        tamX=im.size[0]
        factor=float(numColSalida)/tamX
        #factor de escalado para que tenga el numero de columnas deseado
        #convierto a float el numerador para que el resultado sea flotante
        #si no trunca los decimales y factor seria 0
        try:
            if factor<1:
                tam= int(im.size[0]*factor),int(im.size[1]*factor) #creo una tupla
                out = im.resize(tam)
                out.save(imgSalida)
            else:
                #si el factor es mayor o igual que 1 hace una copia
                im.save(imgSalida)
        except Exception, e:
            e.message="No se ha podido escribir la imagen " + imgSalida + ". Es posible que no tenga permiso de escritura en el directorio. No deben haber ni acentos, ni eñes en la ruta"
            return e

    def descargaYgrabaArchivo(self,oConsultasPg,nomTabla,nomCampoBytea,condWhere,lvCondWhere,nom_arch):
        """
        Descarga un archivo de un campo bytea y lo escribe en el archivo dado.
        Si el archivo existe, se mantiene y devuelve una excepcion.

        @type oConsultasPg: ConsultasPg
        @param oConsultasPg: Objeto de la clase ConsultasPg inicializado
        @type nomTabla: string
        @param nomTabla: Nombre completo de la tabla, incluido el esquema: comun.trabajos
        @type nomCampoBytea: string
        @param nomCampoBytea: Nombre del camo que contiene los datos biarios a descargar.
        @type condWhere: string
        @param condWhere: Condicion where que se ha de cumplir para seleccionar el registro adecuado.
        Ejemplo: "id_trabajo=%s and provincia=%s"

        @type lvCondWhere: lista
        @param lvCondWhere: lista de valores correspondientes a los %s que hay la cadena condWhere.
        @type nom_arch: string
        @param nom_arch: nombre del archivo a crear con el contenido del campo bytea.

        @return: True si todo va bien. Exception si hay algun error.
        """
        #primero comprueba que el archivo no haya sido descargado ya,
        #en tal caso no hace falta que vuelva a descargarse del servidor
        if os.path.exists(nom_arch):
            mens="El archivo ya estaba descargado en: " + nom_arch
            return Exception(mens)

        #El archivo no ha sido descargado

        cursor=oConsultasPg.recuperaDatosTablaBytea(nomTabla, [nomCampoBytea], condWhere,lvCondWhere)
        if isinstance(cursor, Exception):
            mens=unicode("No se pudo descargar: ","utf-8")
            mens=mens+ cursor.message
            return Exception(mens)
        n=cursor.rowcount
        if not(n == 1):
            mens=unicode("Resultados incorrectos. Consulta:","utf-8")
            mens=mens + oConsultasPg.consulta
            return Exception(mens)
        tuplaValores=cursor.fetchone()
        binary=tuplaValores[0]
        if binary==None:
            return Exception("El registro no tiene datos binarios en el campo archivo")
        oArchivos=Archivos()
        res=oArchivos.EscribeDatBinarios(nom_arch, binary)
        if isinstance(res,Exception):
            mens="No se pudo escribir el archivo: " + res.message
            return Exception(mens)
        return True

class ConsultasPg(object):
    """
    Inserta y recupera datos de postgres. Puede recuperar
    e insertar campos con geometría o datos binarios de imagenes.
    de campos bytea

    Tiene una propiedad, denominada consulta que devuelve la cosulta efectuada
    """

    def __init__(self, oConectaPg):
        """
        Constructor
        @type  oConectaPg: una instancia de la clase conectaPg
        @param oConectaPg: una instancia de la clase conectaPg
        """
        self.__cursor=oConectaPg.cursor
        self.__conn=oConectaPg.conn
        self.__consulta=None
        self.__oGeneraExpresionesPsycopg2=GeneraExpresionesPsycopg2()
        self.oConectaPg=oConectaPg

    def __set_oConectaPg(self,oConectaPg):
        self.__oConectaPg=oConectaPg
    def __get_oConectaPg(self):
        return self.__oConectaPg
    def __get_oGeneraExpresionesPsycopg2(self):
        return self.__oGeneraExpresionesPsycopg2
    def insertaDatosTablaBytea(self,nombreTabla,listaCampos, listaValores, nombreCampoBytea):
        """
        Inserta un registro en una tabla con un campo bytea

        @type  nombreTabla: string
        @param nombreTabla: Nombre de la tabla. ej1:"imagenes" ej2: "esquemaPG.imagenes".
        @type  listaCampos: string
        @param listaCampos: Lista con los nombres de los campos. Ej [nombre, direccion, img]. El último nombre en la lista debe ser el del campo bytea
        @type  listaValores: lista
        @param listaValores: Lista con los valores de los campos. El último valor debe ser la variable de los datos binarios a almacenar en el campo Bytea. Es lo que devuelve el método leeDatBinarios.
        @return: True si se ha realizado. Exception si no se ha podido realizar.
        """
        i=0
        for campo in listaCampos:
            if campo==nombreCampoBytea:
                listaValores[i]=psycopg2.Binary(listaValores[i])#sustituyo los
                    #datos binarios por los datos binarios escapados
            i=i+1

        tuplaValores=tuple(listaValores)
        #c=GeneraExpresionesPsycopg2()
        c=self.oGeneraExpresionesPsycopg2
        consulta=c.generaInsertPsycopg2(nombreTabla, listaCampos)
        self.__consulta=consulta

        try:
#            self.__cursor.execute("insert into %s (%s,%s,%s) values (%s,%s,%s)", (nombreTabla,listaCampos[0],listaCampos[1],listaCampos[2],listaValores[0],listaValores[1],psycopg2.Binary(listaValores[2])))
            self.__cursor.execute(consulta, tuplaValores)
            self.__conn.commit()
            return True
        except Exception, e:
            self.__conn.commit()
            e.message=unicode(e.message,"utf-8")
            return e

    def insertaDatosTablaGeom(self,nombreTabla,listaCampos, listaValores,esMulti, nombreCampoGeom, epsg ):
        """
        Inserta un registro en una tabla con un campo de geometria

        @type  nombreTabla: string
        @param nombreTabla: Nombre de la tabla. ej1:"imagenes" ej2: "esquemaPG.imagenes".
        @type  listaCampos: string
        @param listaCampos: Lista con los nombres de los campos. Ej [nombre, direccion, img]. El último nombre en la lista debe ser el del campo bytea
        @type  listaValores: lista
        @param listaValores: Lista con los valores de los campos. El último valor debe ser la variable de los datos binarios a almacenar en el campo Bytea. Es lo que devuelve el método leeDatBinarios.
        @type  esMulti: Booleano
        @param esMulti: Si es multi, se deja como estaba, si no, se convierte a multi.
        @type  nombreCampoGeom: srtring
        @param nombreCampoGeom: nombre del campo de geometría. Debe estar incuído en listaCampos.
        @type epsg: string
        @param epsg: codigo EPSG de la tabla donde se insertará la geometria
        @return: True si se ha realizado. Exception si no se ha podido realizar.
        """

        tuplaValores=tuple(listaValores)
        #c=GeneraExpresionesPsycopg2()
        c=self.oGeneraExpresionesPsycopg2
        consulta=c.generaInsertPsycopg2Geom(nombreTabla, listaCampos, esMulti, nombreCampoGeom, epsg)
        self.__consulta=consulta
        try:
#            self.__cursor.execute("insert into %s (%s,%s,%s) values (%s,%s,%s)", (nombreTabla,listaCampos[0],listaCampos[1],listaCampos[2],listaValores[0],listaValores[1],psycopg2.Binary(listaValores[2])))
            self.__cursor.execute(consulta, tuplaValores)
            self.__conn.commit()
            return True
        except Exception, e:
            self.__conn.commit()
            e.message=unicode(e.message,"utf-8")
            return e

    def insertaDatos(self,nombreTabla,listaCampos, listaValores,nombreCampoBytea=None, esMulti=False, nombreCampoGeom=None, epsg=None, returning=None):
        """
        Inserta un registro en una tabla. La tabla puede contener un campo bytea, para almacenar archivos
        o un campo de geometría, o ambos.

        El valor del campo Bytea es lo que devuelve el método leeDatBinarios de la clase Archivos.
        El valor del campo de geometrái debe ser como el ejemlo que sigue:
            - 'POLYGON((716929.025919 4350081.309705,716981.909594 4350174.233877,...,716929.025919 4350081.309705))'
            - se obtiene con el método exportToWkt() de la clase qgsGeometry de QGis
                - seleccion = layer.selectedFeatures()
                - for objeto in seleccion:
                    - geom=objeto.geometry()
                    - geomT=str(geom.exportToWkt())

        Si returning es una lista de campos, puede devolver los valores insertados en la tabla
        para esos campos. Estos valores se pueden recuperar con cursor.fetchall()

        @type  nombreTabla: string
        @param nombreTabla: Nombre de la tabla. ej1:"imagenes" ej2: "esquemaPG.imagenes".
        @type  listaCampos: string
        @param listaCampos: Lista con los nombres de los campos, incluídos, en su caso, el nombre del campo de geometría, y el nombre del campo bytea . Ej [nombre, direccion, img, geom].
        @type  listaValores: lista
        @param listaValores: Lista con los valores de los campos. El campo Bytea es lo que devuelve el método leeDatBinarios de la clase Archivos.
        @type  esMulti: Booleano
        @param esMulti: Si es multi, se deja como estaba, si no, se convierte a multi.
        @type  nombreCampoGeom: srtring
        @param nombreCampoGeom: nombre del campo de geometría. Debe estar incuído en listaCampos.
        @type epsg: string
        @param epsg: codigo EPSG de la tabla donde se insertará la geometria
        @type returning: lista
        @param returning: Lista de campos de la insercion a retornar. Se pueden recuperar con cursor.fetchall(), despues de la insercion. Si es None, no retorna nada.
        @return: True si lo consigue. Una instancia del error en caso contrario.
        """
        if nombreCampoBytea is not None:
            i=0
            for campo in listaCampos:
                if campo==nombreCampoBytea:
                    listaValores[i]=psycopg2.Binary(listaValores[i])#sustituyo los
                        #datos binarios por los datos binarios escapados
                i=i+1

        #c=GeneraExpresionesPsycopg2()
        c=self.oGeneraExpresionesPsycopg2
        if nombreCampoGeom is None:
            consulta=c.generaInsertPsycopg2(nombreTabla, listaCampos)
        else:
            consulta=c.generaInsertPsycopg2Geom(nombreTabla, listaCampos, esMulti, nombreCampoGeom, epsg)
        try:
#            self.__cursor.execute("insert into %s (%s,%s,%s) values (%s,%s,%s);", (nombreTabla,listaCampos[0],listaCampos[1],listaCampos[2],listaValores[0],listaValores[1],psycopg2.Binary(listaValores[2])))
            if returning != None:
                consulta=consulta + " returning "
                for campo in returning:
                    consulta=consulta + campo + ","
                consulta=consulta[:-1]#elimino la ultima coma
            tuplaValores=tuple(listaValores)
            self.__consulta=consulta
            self.__cursor.execute(consulta, tuplaValores)
            self.__conn.commit()
            return True
        except Exception, e:
            self.__conn.commit()
            e.message=unicode(e.message,"utf-8")
            return e

    def insertaDatosDic(self,nombreTabla,dicValores,nombreCampoBytea=None, esMulti=False, nombreCampoGeom=None, epsg=None, returning=None):
        """
        Es igual que L{insertaDatos}, solo que, en vez de recibir dos listas, una con los nombres de los campos
        y otra con los nombres de los valores, recibe un diccionario campo:valor
        """
        resp=self.insertaDatos(nombreTabla,dicValores.keys(), dicValores.values(),nombreCampoBytea, esMulti, nombreCampoGeom, epsg, returning)
        return resp

    def updateDatos(self,nombreTabla,listaCampos, listaValores,nombreCampoBytea=None, esMulti=False, nombreCampoGeom=None, epsg=None, condicionWhere=None, listaValoresCondWhere=None,returning=None):
        """
        Actualiza un registro en una tabla. La tabla puede contener un campo bytea, para almacenar archivos
        o un campo de geometría, o ambos. El valor del campo Bytea es lo que devuelve el método leeDatBinarios de la clase Archivos.

        El valor del campo de geometria debe ser como el ejemlo que sigue:
            - 'POLYGON((716929.025919 4350081.309705,716981.909594 4350174.233877,...,716929.025919 4350081.309705))'

        Se obtiene con el método exportToWkt() de la clase qgsGeometry de QGis
            - seleccion = layer.selectedFeatures()
            - for objeto in seleccion:
            - geom=objeto.geometry()
            - geomT=str(geom.exportToWkt())

        @type  nombreTabla: string
        @param nombreTabla: Nombre de la tabla. ej1:"imagenes" ej2: "esquemaPG.imagenes".
        @type  listaCampos: string
        @param listaCampos: Lista con los nombres de los campos. Ej [nombre, direccion, img]. El último nombre en la lista debe ser el del campo bytea
        @type  listaValores: lista
        @param listaValores: Lista con los valores de los campos. El campo Bytea es lo que devuelve el método leeDatBinarios de la clase Archivos.
        @type  esMulti: Booleano
        @param esMulti: Si es multi, se deja como estaba, si no, se convierte a multi.
        @type  nombreCampoGeom: srtring
        @param nombreCampoGeom: nombre del campo de geometría. Debe estar incuído en listaCampos.
        @type epsg: string
        @param epsg: codigo EPSG de la tabla donde se insertará la geometria
        @type condicionWhere: string
        @param condicionWhere: condicion que ha de cumplir el registro para ser actualizado. Ejemplo: id=%s and lugar=%s or lugar=%s.
            Como se ve no hay que poner los valores, hay que poner %s. Esos valores los debe introcir Psycopg2 en cursor.execute(cad,valores)
            Deben estar en la lista valores. Se hace así por seguridad. pysicopg2 escapa los caracteres correctamente.
        @type listaValoresCondWhere: lista
        @param listaValoresCondWhere: lista de valores de la condicion Where, que han de sustituir a los %s de la condicion Where.
        @return: True si lo consigue. Una instancia del error en caso contrario.
        """
        if nombreCampoBytea is not None:
            i=0
            for campo in listaCampos:
                if campo==nombreCampoBytea:
                    listaValores[i]=psycopg2.Binary(listaValores[i])#sustituyo los
                        #datos binarios por los datos binarios escapados
                i=i+1

        #c=GeneraExpresionesPsycopg2()
        c=self.oGeneraExpresionesPsycopg2
        if nombreCampoGeom is None:
            consulta=c.generaUpdatePsycopg2(nombreTabla, listaCampos, condicionWhere)
        else:
            consulta=c.generaUpdatePsycopg2Geom(nombreTabla, listaCampos, esMulti, nombreCampoGeom, epsg, condicionWhere)
            if returning != None:
                consulta=consulta + " returning "
                for campo in returning:
                    consulta=consulta + campo + ","
                consulta=consulta[:-1]#elimino la ultima coma
        try:
            if listaValoresCondWhere!=None:
                listaValores2=listaValores[:]#hago una copia para no modificar la lista en el exterior del metodo
                listaValores2.extend(listaValoresCondWhere)#añade una lista a otra
            tuplaValores=tuple(listaValores2)
            self.__consulta=consulta
            self.__cursor.execute(consulta, tuplaValores)
            self.__conn.commit()
            return True
        except Exception, e:
            self.__conn.commit()
            e.message=unicode(e.message,"utf-8")
            return e

    def deleteDatos(self,nombreTabla,dicCondWhere=None):
        """
        Borra los registros de una tabla que coinciden con todos los valores de campo de
        dicCondWhere. Si dicCondWhere==None, borra todos los registros de la tabla
        @return: True si todo va bien, Exception si hay algún problema.
        """
        if dicCondWhere==None or len(dicCondWhere)==0:
            consulta= "delete from " + nombreTabla#borra todos los registros
        else:
            c=GeneraExpresionesPsycopg2()
            condWhere=c.generaWhere(dicCondWhere.keys(),"and")
            consulta="delete from " + nombreTabla + " where " + condWhere
        self.__consulta=consulta
        try:
            if dicCondWhere==None or len(dicCondWhere)==0:
                self.__cursor.execute(consulta)
                self.__conn.commit()
                return True
            else:
                tuplaValores=tuple(dicCondWhere.values())
                self.__cursor.execute(consulta, tuplaValores)
                self.__conn.commit()
                return True
        except Exception, e:
            self.__conn.commit()
            e.message=unicode(e.message,"utf-8")
            return e

    def insertaArchivoPG(self,nombreTabla,nombreCampo,datosBinarios):
        """
        Inserta un archivo en postgres

        @type  nombreTabla: string
        @param nombreTabla: Nombre de la tabla. ej1:"imagenes" ej2: "esquemaPG.imagenes".
        @type  nombreCampo: string
        @param nombreCampo: Nombre del campo bytea destino".
        @type  datosBinarios: datos binarios
        @param datosBinarios: datos binarios del archivo. Se pueden obtener con el metodo leeDatBinarios de esta clase.
        @return: True si se ha realizado. Exception si no se ha podido realizar.
        """

        #el comapo img es de tipo bytea
        consulta="insert into " + nombreTabla + "(" + nombreCampo + ")" + " values (%s)"
        self.__consulta=consulta
        try:
            self.__cursor.execute(consulta, (nombreTabla,nombreCampo,psycopg2.Binary(datosBinarios)))
            self.__conn.commit()
            return True
        except Exception, e:
            self.__conn.commit()
            e.message=unicode(e.message,"utf-8")
            return e

    def recuperaDatosTablaBytea(self, nombreTabla, listaCampos, condicionWhere=None,listaValoresCondWhere=None,bytea_output_to_escape=False,orderBy=None,limit=None):
        """
        Recupera el contenido de una tabla, tenga o no, un campo bytea de postgres

        @type  nombreTabla: string
        @param nombreTabla: Nombre de la tabla. ej1:"imagenes" ej2: "esquemaPG.imagenes".
        @type  listaCampos: Lista de strings
        @param listaCampos: Lista de strings con los nombres de los campos a recuperar. Ej: ["id", "nombre", "img"]
        @type condicionWhere: string
        @param condicionWhere: condicion que ha de cumplir el registro para ser actualizado. Ejemplo: id=%s and lugar=%s or lugar=%s.
            Como se ve no hay que poner los valores, hay que poner %s. Esos valores los debe introcir Psycopg2 en cursor.execute(cad,valores)
            Deben estar en la lista valores. Se hace así por seguridad. pysicopg2 escapa los caracteres correctamente.
        @type listaValoresCondWhere: lista
        @param listaValoresCondWhere: lista de valores de la condicion Where, que han de sustituir a los %s de la condicion Where.
        @type bytea_output_to_escape: boolean
        @param bytea_output_to_escape : Si es True, ejecuta cur.execute("SET bytea_output TO escape;"), necesario para postgres 9.0 o posterior
            con psycopg2 versiones anteriores a la 2.4.1. Si no no recupera bien los campos bytea.
            A partir de la version 2.4.1 ya no hace falta esta linea.
        @return: Una referencia al cursor que contiene las filas seleccionadas. Exception si no se ha podido realizar.
        """

        #c=GeneraExpresionesPsycopg2()
        c=self.oGeneraExpresionesPsycopg2
        try:
            consulta=c.generaSelect(nombreTabla, listaCampos, condicionWhere,orderBy,limit)
            self.__consulta=consulta
        except Exception, e:
            e.message=unicode(e.message,"utf-8")
            return e
        if condicionWhere==None:
            try:
                if bytea_output_to_escape==True:
                    self.__cursor.execute("SET bytea_output TO escape;")
                self.__cursor.execute(consulta)
                self.__conn.commit()
            except Exception, e:
                self.__conn.commit()
                e.message=unicode(e.message,"utf-8")
                return e
        else:
            tuplaValores=tuple(listaValoresCondWhere)
            try:
                self.__cursor.execute("SET bytea_output TO escape;")
                self.__cursor.execute(consulta, tuplaValores)
                self.__conn.commit()
            except Exception, e:
                self.__conn.commit()
                e.message=unicode(e.message,"utf-8")
                return e
#            if self.__cursor.rowcount==0:
#                mens="No hay ninguna imagen en la BDA que coincida con este criterio: " + condicionWhere
#                raise Exception(mens)
#            if self.__cursor.rowcount > 1:
#                mens="Hay varias imagenes en la BDA que coinciden con este criterio: " + condicionWhere
#                raise Exception(mens)
        return self.__cursor

    def recuperaDatosTablaByteaDic(self, nombreTabla, listaCampos, condicionWhere=None,listaValoresCondWhere=None,bytea_output_to_escape=False, orderBy=None,limit=None):
        """
        La documentacion es la misma que la del metodo L{recuperaDatosTablaBytea}.
        La diferencia es que este metodo no devuelve un cursor, si no una lista con todas las
        filas seleccionadas. Cada elemento de la lista es un diccionario {nombre_campo:valor,nombre_campo:valor,....}
        Si un valor de campo no tiene valor, el valor para el campo en el diccionario es None.

        @return: Una lista de diccionarios. Un diccionario por fila seleccionada.
            Si no hay selección, la lista estará vacía.
            Exception si hay error.
        """
        cursor=self.recuperaDatosTablaBytea(nombreTabla, listaCampos, condicionWhere,listaValoresCondWhere,bytea_output_to_escape,orderBy,limit)
        if isinstance(cursor,Exception):
            return cursor
        filas=cursor.fetchall()
        listaDic=[]
        for fila in filas:
            dic={}
            for i,valor in enumerate(fila):
                dic[listaCampos[i]]=valor
            listaDic.append(dic)
        return listaDic

    def sacaNombreTablasEsquema_cursor(self,esquema):
        """
        Devuelve un cursor con los nombres de las tablas de un esquema.

        Saber las tablas de un schema. pg_tables es una vista
            - SELECT tablename FROM pg_tables WHERE schemaname = 'public'

        @type  esquema: string
        @param esquema: Nombre del esquema
        @return: Una referencia al cursor que contiene las filas seleccionadas. Exception si no se ha podido realizar.

        """
        consulta="SELECT tablename FROM pg_tables WHERE schemaname = %s"
        lis=[]
        lis.append(esquema)
        try:
            self.__cursor.execute(consulta,lis)
            if self.__cursor.rowcount==0:
                mens="No hay ninguna tabla en el esquema " + esquema
                mens=unicode(mens,"utf-8")
                return Exception(mens)

        except Exception, e:
            self.__conn.commit()
            e.message=unicode(e.message,"utf-8")
            return e
        self.__consulta=consulta

        return self.__cursor

    def sacaNombreTablasEsquema_lista(self,esquema,anteponerEsquema=False):
        """
        Devuelve una lista con los nombres de las tablas de un esquema.
        @type esquema: string
        @param esquema: Nombre del esquema
        @return: Una lista que contiene las filas seleccionadas. Exception si no se ha podido realizar.
        """
        oCursor=self.sacaNombreTablasEsquema_cursor(esquema)
        if isinstance(oCursor,Exception):
            return oCursor#devuelve la excepcion

        listaValores=oCursor.fetchall()#es una lista de tuplas.
                #cada tupla es una fila. En este caso, la fila tiene un
                #unico elemento, que es el nombre del campo.
        listaNombreTablas=[]
        for fila2 in listaValores:
            valor=fila2[0]
            if anteponerEsquema==True:
                valor=esquema + "." + valor
            listaNombreTablas.append(valor)
        return listaNombreTablas


    def sacaNombresCamposTabla_cursor(self,esquema, nomTabla):
        """
        Devuelve un cursor con los nombres de los campos de una tabla.
        @type  esquema: string
        @param esquema: nombre del esquema que contiene la tabla. Ejemplo "h30"
        @type  nomTabla: string
        @param nomTabla: nombre de la tabla. Ejemplo "linde"
        @return: Una referencia al cursor que contiene las filas seleccionadas. Exception si no se ha podido realizar.

        saber las columnas de una tabla:
        SELECT column_name FROM information_schema.columns WHERE table_schema='h30' and table_name = 'linde';
        """

        consulta="SELECT column_name FROM information_schema.columns WHERE table_schema=%s and table_name = %s";

        lis=[]
        lis.append(esquema)
        lis.append(nomTabla)
        try:
#            self.__cursor.execute(consulta,lis)
            self.__cursor.execute(consulta,lis)
            if self.__cursor.rowcount==0:
                mens=unicode("No hay ninguna tabla en el esquema " + esquema,"utf-8")
                self.__conn.commit()
                return Exception(mens)

        except Exception, e:
            e.message=unicode(e.message,"utf-8")
            return e
        self.__consulta=consulta
        self.__conn.commit()
        return self.__cursor

    def sacaNombresCamposTabla_lista(self,esquema, nomTabla):
        """
        Devuelve una lista con los nombres de los campos de una tabla.
        @type  esquema: string
        @param esquema: nombre del esquema que contiene la tabla. Ejemplo "h30"
        @type  nomTabla: string
        @param nomTabla: nombre de la tabla. Ejemplo "linde"
        @return: una lista con los nombres de los campos. Exception si hay error
        """
        oCursor=self.sacaNombresCamposTabla_cursor(esquema, nomTabla)
        if isinstance(oCursor,Exception):
            return oCursor#devuelve la excepcion

        listaValores=oCursor.fetchall()#es una lista de tuplas.
                #cada tupla es una fila. En este caso, la fila tiene un
                #unico elemento, que es el nombre del campo.
        listaNombreCampos=[]
        for fila2 in listaValores:
            valor=fila2[0]
            listaNombreCampos.append(valor)
        return listaNombreCampos

    def mueveRegistros(self,tablaOrigen,tablaDestino,listaCamposCopiar,listaCamposNoCopiar=None,dicCondWhere={},puedenSerVarios=False,puedenSerCero=False,dicValoresCambiar=None,dicValoresAdd=None,borrarOrigen=False, listaCamposReturning=None):
        """
        Copia registros de tablaOrigen a tablaDestino.
        @type tablaOrigen: string
        @param tablaOrigen: nombre completo de la tabla origen.
        @type tablaDestino: string
        @param tablaDestino: nombre completo de la tabla destino.
        @type listaCamposCopiar: lista, o string
        @param listaCamposCopiar: lista con los campos a copiar de una tabla a otra.
            Si se pasa "todos", se extraen todos los nombres de campos de la tabla.
        @type listaCamposNoCopiar: lista
        @param listaCamposNoCopiar: Lista con los campos a eliminar de la lista listaCamposCopiar.
            Está pensado para cuando listaCamposCopiar sea "todos", pero que haya que eliminar
            algún campo, por ejemplo el id, o gid. Este campo debe ser serial primary key,
            y lo debe asignar la BDA.
        @type dicCondWhere: diccionario
        @param dicCondWhere: diccionario nombreCampo:condición a cumplir para ser seleccionado
            Deben estar en la lista valores. Se hace así por seguridad. pysicopg2 escapa los caracteres correctamente.
        @type puedenSerVarios: booleano
        @param puedenSerVarios: Si es false y resultan varios registros seleccionados de la
            tabla origen, se genera un error.
        @type puedenSerCero: booleano
        @param puedenSerCero: Si es false y resultan cero registros seleccionados de la
            tabla origen, se genera un error.
        @type dicValoresCambiar: diccionario
        @param dicValoresCambiar: diccionario nombreCampo: valor. Se puede utilizar para cambiar
            algún valor de la tabla destino, es decir, la tabla destino tendrá, en los campos
            indicados los valores de este diccionario, no los de la tabla origen.
        @type borrarOrigen: booleano
        @param borrarOrigen: Si es True, se borran todos los registros seleccionados en la tabla
            origen.
        @type listaCamposReturning: lista
        @param listaCamposReturning: si no es None, se devuelve un diccionario con
            los valores de los campos indicados. Estos valores son devueltos por el
            servidor, después de insertar el registro.
        @raise Exception: Si, de la selección, resultan varios registros y debe ser uno.
            Si la selección o la inserción generan algún error.
        @return: Un diccionario con los campos especificados en listaCamposReturning.
            Un diccionario vacío, si listaCamposReturning es None.
        """
        if listaCamposCopiar=="todos":
            lista=tablaOrigen.split(".")
            listaCampos=self.sacaNombresCamposTabla_lista(esquema=lista[0], nomTabla=lista[1])
            if isinstance(listaCampos,Exception):
                raise Exception(listaCampos.message)
        else:
            listaCampos=listaCamposCopiar
        if listaCamposNoCopiar.__class__.__name__=="list":
            oUtilidadesListas=pyGenGas.UtilidadesListas()
            listaCampos=oUtilidadesListas.eliminaEltosLista(listaEltos=listaCampos,listaEliminar=listaCamposNoCopiar, genError=True)
            if isinstance(listaCampos,Exception):
                raise Exception(listaCampos.message)

        expre=GeneraExpresionesPsycopg2()
        condicionWhere=expre.generaWhere(listaCampos=dicCondWhere.keys(), and_or="and")
        dicSelOrigen=self.recuperaDatosTablaByteaDic(nombreTabla=tablaOrigen, listaCampos=listaCampos, condicionWhere=condicionWhere, listaValoresCondWhere=dicCondWhere.values(), bytea_output_to_escape=False)
        if isinstance(dicSelOrigen,Exception):
            raise Exception(dicSelOrigen.message)
        n=len(dicSelOrigen)
        if n==0:
            if puedenSerCero==False:
                raise Exception("Resultaron seleccionados cero elementos de la tabla " + tablaOrigen + " que cumplan " + condicionWhere)
            else:
                return {}
        if n>1:
            if puedenSerVarios==False:
                raise Exception("Resultaron seleccionados " + str(n)+ " elementos de la tabla " + tablaOrigen + " que cumplan " + condicionWhere)

        if dicValoresCambiar.__class__.__name__=="dict":
            for campoCambiar in dicValoresCambiar.keys():
                if not(campoCambiar in listaCampos):
                    raise Exception("El campo a cambiar de valor " + campoCambiar + " no esta en la lista de campos a recuperar de la tabla.")
            for dic in dicSelOrigen:
                dic.update(dicValoresCambiar.items())
        if dicValoresAdd.__class__.__name__=="dict":
            for dic in dicSelOrigen:
                dic.update(dicValoresAdd.items())
        for dic in dicSelOrigen:
            resp=self.insertaDatosDic(nombreTabla=tablaDestino, dicValores=dic, nombreCampoBytea=None, esMulti=None, nombreCampoGeom=None, epsg=None, returning=listaCamposReturning)
            if isinstance(resp,Exception):
                raise Exception(resp.message)
            if borrarOrigen==True:
                resp=self.deleteDatos(nombreTabla=tablaOrigen, dicCondWhere=dicCondWhere)
                if isinstance(resp,Exception):
                    raise Exception(resp.message)
        dic={}
        if listaCamposReturning.__class__.__name__=="list":
            resp=self.oConectaPg.cursor.fetchall()
            try:
                fila=resp[0]
                for i,columna in enumerate(listaCamposReturning):
                    dic[columna]=fila[i]
                return dic
            except Exception, e:
                raise Exception("Error en los campos returning de mueveRegistros: " + e.message)
        else:
            return dic

    def creaUsuario(self, nombreUsuarioUnicode,passwordUnicode):
        """
        Crea un usuario postgres a partir del nombre y el password
        Nombre usuario y password deben estar en Unicode
        @raise exception: Exception
        """
        consulta = self.toUtf8("create user ") + nombreUsuarioUnicode + self.toUtf8(" with encrypted password '") + passwordUnicode + self.toUtf8("' login")
        try:
            self.__cursor.execute(consulta)
            self.__conn.commit()
        except Exception,e:
            self.__conn.commit()
            mens=self.toUtf8("No se pudo crear el usuario de postgres ") + nombreUsuarioUnicode
            mens=self.toUtf8(". El servidor respondió: ") + e.message
            raise Exception(mens)
        return True

    def borraUsuario(self,nombreUsuarioUnicode):
        """
        Borra un usuario de postgres.
        @raise exception: Exception
        """
        consulta=self.toUtf8("drop user ") + nombreUsuarioUnicode
        try:
            self.__cursor.execute(consulta)
            self.__conn.commit()
        except Exception,e:
            self.__conn.commit()
            mens=self.toUtf8("No se pudo borrar el usuario de postgres ") + nombreUsuarioUnicode + self.toUtf8(". Debe hacerlo Ud. Manualmente.")
            mens=self.toUtf8(". El servidor respondió: ") + e.message
            raise Exception(mens)
        return True

    def addUsuarioAGrupo(self,nombreUsuarioUnicode,grupoUnicode):
        """
        Añade un usuario a un grupo de postgres
        @raise exception: Exception
        """

        consulta=self.toUtf8("grant ") +  grupoUnicode + self.toUtf8(" to ") + nombreUsuarioUnicode
        try:
            self.__cursor.execute(consulta)
            self.__conn.commit()
        except Exception,e:
            self.__conn.commit()
            mens=self.toUtf8("No se pudo añadir el usuario de postgres ") + nombreUsuarioUnicode + self.toUtf8(" al grupo ") + grupoUnicode
            mens=self.toUtf8(". El servidor respondió: ") + e.message
            raise Exception(mens)
        return True

    def deleteUsuarioDeGrupo(self,nombreUsuarioUnicode,grupoUnicode):
        """
        Borra un usuario de un grupo de postgres
        @raise exception: Exception
        """
        consulta=self.toUtf8("revoke ") +  grupoUnicode + self.toUtf8(" from ") + nombreUsuarioUnicode
        try:
            self.__cursor.execute(consulta)
            self.__conn.commit()
        except Exception,e:
            self.__conn.commit()
            mens=self.toUtf8("No se pudo eliminar el usuario de postgres ") + nombreUsuarioUnicode + self.toUtf8(" del grupo ") + grupoUnicode
            mens=self.toUtf8(". El servidor respondió: ") + e.message
            raise Exception(mens)
        return True



    def toUtf8(self,mens):
        return unicode(mens,"utf-8")

    def sacaNombresCamposTabla_lista2(self,nomTablaCompleto):
        """
        Devuelve una lista con los nombres de los campos de una tabla.
        @type nomTablaCompleto: string
        @param nomTablaCompleto: Nombre de la tabla, incluido el esquema.
        @return: una lista con los nombres de los campos. Exception si hay error
        """
        lista=str.split(nomTablaCompleto,".")
        esquema=lista[0]
        tabla=lista[1]
        return self.sacaNombresCamposTabla_lista(esquema, tabla)

    def __get_consulta(self):
        return self.__consulta

    consulta=property(__get_consulta,"Solo lectura. La consulta realizada")
    oConectaPg=property(__get_oConectaPg,__set_oConectaPg,"Objeto de la clase ConectaPg")
    oGeneraExpresionesPsycopg2=property(__get_oGeneraExpresionesPsycopg2,"Objeto de la clase GeneraExpresionesPsycopg2")

