domingo, 23 de diciembre de 2012

Feliz Navidad.

Aunque este blog lo creé en 2011, no fue hasta julio de 2012 que me lo tomé un poco más en serio. Al principio lo iba a usar como blog de notas personal, con acceso privado para escribir chuletillas que uso de vez en cuando en el trabajo, pero por motivos que no vienen al caso decidí hacerlo público y cambiar un poco el tipo de contenidos.

No fue hasta octubre (no podría jurarlo) que incorporé estadísticas de google, así que los datos no son del todo fiables. Aún así ha sido un placer recibir unas 500 visitas estos meses y comprobar que los códigos que escribo han servido para facilitar el trabajo al visitante.

Estadísticas del año 2012 por países.

Algunos de los post como las series de OpenETL han sido fruto del trabajo de meses de investigación sobre la librería, viendo el código por dentro, haciendo pruebas, etc. Otros en cambio han sido resultados de búsquedas de bing y google sobre temas en concreto que yo después de leer cientos de post he tratado de ordenar. En estos casos siempre he citado, y lo seguiré haciendo, las referencias.

Como he comentado, durante estos meses he tenido unas 500 visitas. La mayoría han sido españolas, aunque otras han venido de países como México, Alemania, EEUU, etc. Cuatro navegadores han sido los usados para ver el blog. Me sorprende que Chrome esté en cabeza de navegadores usados, creo que Google ha hecho un buen marketing.

Estadísticas del año 2012 por navegador.

A todos los visitantes del blog, os deseo que paséis unas muy felices fiestas. Espero veros por aquí el año que viene.

Feliz navidad.

lunes, 17 de diciembre de 2012

Instalando el cliente VPN de Cisco en windows 7.

En algunos windows 7 el cliente de cisco se instala correctamente, pero después no permite realizar ninguna conexión.

En la propia página de cisco encontré la solución: es necesario aplicar las actalizaciones de DNE. El problema es que la instalación de estas dan error el error:

Error 27850: unable to manage network component

Así que una vez más, google al rescate.

El proceso para instalar el cliente quedaría de la siguiente forma:

Primero hay que modificar el registro de windows cambiando el valor de HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\MaxNumFilters desde 8 a 14 (en decimal).

Después hay que instalar el Citrix DNE update . Se descarga desde:

  • ftp://files.citrix.com/dneupdate.msi for 32-bit
  • ftp://files.citrix.com/dneupdate64.msi for 64-bit

La información en la propia página de Citrix.

Por último se instala el cliente, que se descarga también de Citrix (en este caso hace falta crearse una cuenta).

Saludos.

lunes, 10 de diciembre de 2012

Problemas con WSGEN. Métodos duplicados en el wsdl

Hoy al generar un ws con wsgen, el wsdl me ha dado el warning:

cvc-identity-constraint.4.2.2: Duplicate key value [getCuentas] declared for identity constraint "message" of element 
 "definitions".

Lo que pasaba es que en el archivo wsdl, uno de los métodos me aparecía duplicado. Este problema estaba causado porque había definido la interfaz en la forma:

 @WebService(targetNamespace = "http://xxx.xxx.com", name = "XxxxWSInterface")
@SOAPBinding(style = SOAPBinding.Style.RPC, use = SOAPBinding.Use.LITERAL)
public interface BcieWSInterface {

Casualmente me fijé que el método que salía por duplicado era el que devolvía Tipo_dato. En realidad no sé porqué estaba ese tipo declarado en la interfaz, creo que dejé a eclipse arreglar algo y me lo añadió.

Al arreglar la interfaz y dejarla sin el Tipo_dato

 @WebService(targetNamespace = "http://xxx.xxx.com", name = "XxxxWSInterface")
@SOAPBinding(style = SOAPBinding.Style.RPC, use = SOAPBinding.Use.LITERAL)
public interface BcieWSInterface {

y volver a generar se creó el wsdl correctamente :D En fin, una cagada mia, que menos mal que resolví en poco tiempo.

Saludos

sábado, 8 de diciembre de 2012

Importando un DMP en Oracle 11g

Ayer perdí mucho tiempo intentando importar un dmp de Oracle 11g. Probando distintas opciones obtenía errores como

imp-00038: could not convert to environment character set's handle
IMP-00000: Import terminated unsuccessfully

o similares. Lo malo de esto es que en muchos post se explicaba la teoría y el porqué falla, pero nadie publicaba una solución clara. Después de muchas consultas pude dar con dicha solución.


Pasos

  1. Colocar el DMP en la ruta de la bd. En mi caso: C:\oracle\admin\test\dpdump
  2. Ejecutar:
    • impdp 'SYS AS SYSDBA' file=MYDAT01.DMP full=yes
      
    • Si no se tiene creado el usuario de oracle, o el tablespace empieza a fallar. En mi caso faltaba el tablespace:
      CREATE TABLESPACE MY_TS_DATA
        DATAFILE 'C:\oracle\admin\test\TS_DATA.dbf' SIZE 400M
        AUTOEXTEND ON NEXT 200K MAXSIZE 800M; 
      
  3. No olvidar que hay que logarse con el usuario de la exportación.

Información asociada

Aquí encontré la solución.

Este post es muy interesante.

Eso es todo. Al final la clave estaba en el 'SYS AS SYSDBA', ya que yo me logaba con otro usuario. Saludos

jueves, 6 de diciembre de 2012

json.parse: bad control character in string literal

Hoy haciendo una aplicación con ajax el navegador me ha dado un error "json.parse: bad control character in string literal".
Imagen de error con el firebug

El error lo daba la llamada
json = $.parseJSON(string);
El problema era que yo estaba generando un objeto json desde java dentro de una stringbuilder, y una cadena contenía el carácter retorno de carro "\n". Buscando por internet encontré cientos de post que comentaban el problema y hacían perder el tiempo, pues daban soluciones que no funcionaban. En este en concreto se habla del api de json y se especifica que otros caracteres pueden fallar. Al final la solución era crear un objeto json desde java. Este es el trozo de código final en la jsp que llama el js.
Map obj = new HashMap();
obj.put("id", new Long(id_mensaje));
obj.put("cuerpo",mensaje.getCuerpo());
obj.put("fechaRecibido",MensajesUtil.formatDate(mensaje.getFechaEnvio()));
obj.put("usuarioOrigen",MensajesUtil.getName(mensaje.getUsuarioOrigen(), cms));
obj.put("usuarioOrigenId",mensaje.getUsuarioOrigen());
String jsonText = org.json.simple.JSONValue.toJSONString(obj);
out.println(jsonText);
Saludos.

sábado, 17 de noviembre de 2012

CProject. Gestor de proyectos en Android

Desde hace tiempo estoy buscando un gestor de proyectos en android que cumpla con mis necesidades, pero no encuentro ninguno. Así que hace 4 días me pregunté: ¿y si retomo mi proyecto de sincronización de tareas Android-OpenERP y le doy un lavado de cara?. Dicho y hecho. En 4 días, con sus noches, he realizado un pequeño proyectito.

CProject - Créditos del proyecto.

Aún le queda mucho por mejorar. Por ahora sólo se pueden crear proyectos nuevos, tareas en dichos proyectos, y salvar toda la información en SQLite en el propio dispositivo. Por supuesto también se pueden migrar los proyectos a OpenERP mediante Web-Services.
CProject - Action bar con botón que ejecuta Web Service para migrar datos a OpenERP.

En mi lista de TODOs para el proyecto tengo en mente mejorar e implementar una serie de cosas, como por ejemplo que se pinte el diagrama de Gantt, asignar etiquetas a los proyectos, estadísticas, popups para introducir fechas, etc.

CProject - Pantalla de introducción de nombre de proyecto.

En sucesivos post iré comentando el estado del proyecto, he incluso cuando termine algunas características más grabaré algún vídeo para subirlo al blog.
Por último, si alguien está interesado en el proyecto, o desea que implemente alguna característica especial, o simplemente quiere colaborar, que contacte conmigo.
Saludos.

jueves, 15 de noviembre de 2012

Custom OnItemClickListener para elementos del ListView

En la entrada anterior realicé un menú contextual para los items de un ListView. El caso es que en mi aplicación quiero que haya una opción por defecto (que también está en el menú contextual) para las pulsaciones simples.

Encontré un post muy interesante que trataba el tema (en inglés), y otro post adaptado del anterior en español.

En mi caso lo he adaptado a mis necesidades, puesto que mis opciones del ListView se leen de base de datos.

A continuación mi código de ejemplo


private void initActivity(){
        setContentView(R.layout.main);
        
        registerForContextMenu(getListView());  
        
        // Habilito click simple en la lista de elementos
        // Hack by http://stackoverflow.com/questions/4852307/listview-onclick-goes-to-a-new-activity
        //libreyextremo.blogspot.com.es/2012/03/tutorial-android-parte-13-navegacion.html
        ListView lv = (ListView)findViewById(android.R.id.list);
        final Intent activity = new Intent(this,CTaskActivity.class);
        OnItemClickListener myclickListView = new OnItemClickListener()
        {
         public void onItemClick(AdapterView parent, View view,int position, long id){
          
           long id_proyecto = getIdProjectByPositionListView(position);
           activity.putExtra("id_proyecto", id_proyecto);
           
           startActivity(activity);
             
         }
         
        };

        setListAdapter(new ArrayAdapter(context,android.R.id.list));
        lv.setOnItemClickListener(myclickListView);
        
     // Cargo los proyectos en el sistema
        showAllProjects();
 }

El código es bastante sencillo, simplemente se trata de hacer un OnItemClickListener persnalizado, asignarlo al ListView y después cargar los items. Ojo, que si se cargan antes los items no se mostrará nada en pantalla.

Por último, initActivity() es un método que llamo desde onCreate() de la actividad.

Saludos.

martes, 13 de noviembre de 2012

Creando un menú context para elementos de ListView

Rediseñando una aplicación que estoy haciendo en Android he decidido añadir menús de contexto a los elementos de una lista (ListView).

Captura de pantalla del menú de contexto.


El primer paso como siempre ha sido buscar la solución en alguno de mis libros de Android, pero resulta que este tipo de menú no viene :(
Menos mal que existe San Google y es fácil encontrar post(API) y post al respecto. De todas formas un resumen rápido de lo que hay que hacer.

El código

Creo un menu_context.xml con las opciones que necesito.

    
     
     
    
    
    
    
    

En el fichero string.xml añado los títulos que se muestran en el menú
string.xml

Editar
Borrar
En el método onCreate llamo a registerForContextMenu
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        registerForContextMenu(getListView());    // Hay que añadir esta linea

        }
Registro el menú en la actividad:
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v,
                                    ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_context, menu);
    }
Y por último se añade el switch para manejar las opciones
@Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        switch (item.getItemId()) {
            case R.id.edit:
                // Tareas a realizar
                return true;
            case R.id.delete:
                // Tareas a realizar
                return true;
            default:
                return super.onContextItemSelected(item);
        }
    }
Y esto es todo. Post rápido al canto. Saludos.

martes, 23 de octubre de 2012

De OrangeHRM a OpenERP con OpenETL.

Después de mis últimos post Migración de datos entre distintas instancias de OpenERP usando OpenETL y Carga de datos en OpenERP usando OpenETL y aprovechando que estamos realizando una migración en la empresa desde OrangeHRM a OpenERP que mejor forma de cerrar el círculo que presentar en forma de post el proceso de migración que hemos seguido.

El escenario:

Para migrar los datos hemos partido de los archivos .csv que se generan desde OrangeHRM. Como dichos archivos contienen información de la empresa no los voy a adjuntar con el post, simplemente me limitaré a describir los campos. También partimos de un OpenERP que ya tiene cargados datos como países, provincias, y empresa. Para realizar las llamadas al xmlrpc he usado al usuario admin, el cual ya pertenece a la empresa. De esta forma el campo company_id se ha ajustado automáticamente.
Los archivos .csv contienen los siguientes campos:
Empleados.csv:
  • empID: Identificación del empleado en Orange.
  • lastName: Apellidos del empleado.
  • firstName: Primer nombre del empleado.
  • middleName: Segundo nombre del empleado.
  • street1: Dirección del empleado.
  • street2: Campo de apoyo para street1.
  • city: Municipio.
  • state: Provincia.
  • zip: Código postal.
  • gender: Género.
  • birthDate: Fecha de nacimiento.
  • ssn: Número de la seguridad social.
  • workStation: Departamento al que pertenece el empleado.
Los campos middleName, street1, street2, city, state, zip, workStation,birthDate pueden estar vacíos en el archivo .csv, por lo que hay que controlar estos casos.
dptos.csv:
  • workStation: Nombre del departamento en el sistema.
cargos.csv:
  • empId: Identificación del empleado en Orange.
  • empStatus: Cargo del empleado en la empresa.
El modelo de datos relacionados afectado de OpenERP se presenta a continuación. Lo he simplificado bastante y sólo he puesto los campos de los objetos que se van a cargar con los valores de los .csv.

Si te alejas de la pantalla y te pones bizco, verás a un tio bailando. En realidad es el diagrama de clases simplificado de objetos OpenERP.


A tener en cuenta:

Observando el modelo de datos se puede ver que muchos objetos están relacionados entre sí. Esto implica que si queremos cargar los empleados, antes tenemos que tener cargados en el sistema las direcciones. Este mismo comportamiento nos sucede con los departamentos, puestos de trabajo, etc.
Para solucionar este inconveniente he usado subtareas de OpenETL. El archivo subjob_example.py contiene un ejemplo para el uso de subtareas con OpenETL. El funcionamiento es bastante sencillo. Simplemente en vez de ejecutar la tarea que queremos convertir en subtarea, crearemos un nuevo componente de tipo subtarea con ella. Después a la tarea padre le pasamos como parámetro dicha subtarea.
En el código:
job_ = openetl.job([csv_in1,datos_ajustados,openobject_out2])  # Para poder relacionar direcciones con personas, las direcciones deben estar cargadas
subjob = openetl.component.transform.subjob(job_)              # en el sistema. Las cargo previamente en una subtarea.

job1=openetl.job([subjob_cargos,subjob_dptos,subjob_paises,subjob,csv_in1,datos_ajustados,openobject_out1])

job1.run()

Las subtareas implicadas son son:
  • subjob_cargos: Carga las categorías de los empleados.
  • subjob_dptos: Carga los departamentos de la empresa.
  • subjo_paises: Realiza correspondencia de países de OpenERP con Orange.
  • subjob: Carga las direcciones de los empleados.
Y los diagramas de cada subtarea:


Diagramas de subjob_cargos y subjob_dptos.



Diagramas de subjob_paises y subjob.


Para relacionar los objetos de las subtareas con la carga final, la de los empleados, he usado un pequeño truco. Vamos a fijarnos en la lista de categorías de empleados (subjob_cargos).
Al leer las categorías desde el csv inicial, las he pasado por una transformación que ejecuta una función (preprocess_cargos):
lista_cargos = {}
def preprocess_cargos(self, channels):
    for trans in channels['carga_cargos']:
        for d in trans:
            lista_cargos[d['empId']] = d['empStatus']
    return None

pres_cargos=openetl.component.transform.map({},preprocess_cargos)
Dicha función lo único que hace es cargar en un diccionario una relación empId-empStatus, es decir, relaciona id de empleado con su categoría.
Más adelante en el código, al realizar la carga de los empleados, consulto dicho diccionario:
def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:            
            . 
            .
            .   
            # Ajuste de cargo    
            d['cargo'] = lista_cargos[d['empId']]
            
    return {'resultado':cdict}  

Y por último en el mapeado del objeto, antes de cargarlo en OpenERP:
openobject_out1 = openetl.component.output.openobject_out( ooconnector, 'hr.employee', { . . . 'job_id':'cargo', } )
En el diagrama también aparece una paso previo por el componente unique. Dicho componente quita los elementos duplicados antes de cargarlos en el sistema. Hay un ejemplo de uso de dicho componente en el fichero unique_csv_data.py.
Otra cosa interesante de esta migración es como se han mapeado los datos del csv a los objetos. El fichero join_example.py contiene un ejemplo que usa map_keys. Dicho ejemplo está muy bien y funciona siempre y cuando se usen componentes “openetl.component.input.data” definidos en el propio archivo de script. El problema es que cuando se lee un archivo de csv no se está usando una entrada “estática”, sino secuencial. De modo que el map_keys es ignorado. La solución en este caso ha sido pasar por parámetro un map_key vacío y realizar el mapeo de datos desde el propio código de la función preprocess.
En el caso de países:
pre_paises=openetl.component.transform.map({},preprocess_paises)



def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:
           
           .
           .
           .     
            # Ajuste de paises
            if d['state'] == "Santo Domingo": 
                d['state'] = lista_paises[62]  # Codigo de Republica Dominicana
            elif d['state'] == "Distrito Nacional":
                d['state'] = lista_paises[62]
            else:
                d['state'] = lista_paises[69] # Codigo de Espagna
            .
            .
            .                            
    return {'resultado':cdict}  

El resultado:

Como en mis anteriores post presento el código completo de la solución obtenida. Evidentemente este script de migración cubre nuestras necesidades concretas, pero es fácil adaptarlo si se necesitan migrar datos diferentes. También se podría haber realizado un script de migración atacando directamente a la base de datos de Orange, aunque por seguir con el ejemplo planteado en el primer post de OpenETL se han usado archivos .csv. En cualquier caso OpenETL también contiene conectores para consultas SQL. El archivo sql_in_example.py contiene un ejemplo con el que se podrían sustituir las llamadas a los csv con consultas sql.
import sys
sys.path.append('..')
 
import openetl
  
#===============================================================================
# Conectores
#===============================================================================
fileconnector_orange=openetl.connector.localfile('/home/carlos/Escritorio/Orange/Empleados.csv')
fileconnector_orange_dptos=openetl.connector.localfile('/home/carlos/Escritorio/Orange/dptos.csv') # Con tratamiento previo
fileconnector_orange_cargos=openetl.connector.localfile('/home/carlos/Escritorio/Orange/cargos.csv') # Con tratamiento previo
ooconnector = openetl.connector.openobject_connector('http://localhost:8069', 'master_viavansi', 'admin', 'admin', con_type='xmlrpc')


#===============================================================================
# Componentes
#===============================================================================
csv_in1= openetl.component.input.csv_in(fileconnector_orange,name='Datos de Orange')
csv_in_dptos= openetl.component.input.csv_in(fileconnector_orange_dptos,name='Departamentos')
csv_in_cargos= openetl.component.input.csv_in(fileconnector_orange_cargos,name='Cargos')



openobject_out1 = openetl.component.output.openobject_out(
     ooconnector,
     'hr.employee',
     {
      'name':'name_csv',
      'ssnid':'ssn',
      'gender':'gender',
      'birthday':'birthDate',
      'address_home_id':'name_csv', # Nombre de la relacion
      'department_id':'workStation',
      'job_id':'cargo',
      }
    )


openobject_out2 = openetl.component.output.openobject_out(
     ooconnector,
     'res.partner.address',
     {
      'name':'name_csv',
      'street':'street1',
      'street2':'street2',
      'zip':'zip',
      'city':'city',
      'country_id':'state',
      }
    )


openobject_out3 = openetl.component.output.openobject_out(
     ooconnector,
     'hr.department',
     {
      'name':'workStation',
      }
    )

# Soporte para carga de datos de cargo de empleado. El Diccionario se carga en subtarea previa
lista_cargos = {}
openobject_out4 = openetl.component.output.openobject_out(
     ooconnector,
     'hr.job',
     {
      'name':'empStatus',
      }
    )

def preprocess_cargos(self, channels):
    for trans in channels['carga_cargos']:
        for d in trans:
            lista_cargos[d['empId']] = d['empStatus']
    return None

pres_cargos=openetl.component.transform.map({},preprocess_cargos)

# Soporte para carga de datos de paises. El Diccionario se carga en subtarea previa
lista_paises = {}

openobject_in1 = openetl.component.input.openobject_in(
                 ooconnector,'res.country',
                 fields=['id','name'],
                 )

def preprocess_paises(self, channels):
    for trans in channels['carga_paises']:
        for d in trans:
            lista_paises[d['id']] = d['name']
    return None

pre_paises=openetl.component.transform.map({},preprocess_paises)

# Soporte transformaciones y componentes


def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:
            # name: no existia,lo creo yo con la suma de los campos 
            
            if d['middleName'] == "":  # En OpenERP, no se separan los campos, hay un unico campo name
                d["name_csv"] = d["firstName"] + str(" ")+ d["lastName"]
            else:
                d["name_csv"] = d["firstName"] + str(" ")+ d["middleName"] +str(" ")+ d["lastName"]
            
            
            if d['gender'] == "M":     # Adaptacion de nomencaltura de datos de Orange a OpenERP
                d['gender'] = 'male'
            else:
                d['gender'] ='female'
                
            # Ajuste de paises
            if d['state'] == "Santo Domingo": 
                d['state'] = lista_paises[62]  # Codigo de Republica Dominicana
            elif d['state'] == "Distrito Nacional":
                d['state'] = lista_paises[62]
            else:
                d['state'] = lista_paises[69] # Codigo de Espagna
                
            # Ajuste de cargo    
            d['cargo'] = lista_cargos[d['empId']]
            
    return {'resultado':cdict}            

datos_ajustados=openetl.component.transform.map({},preprocess)  # Como leo un flujo de datos, no hay key_map. key_maps es para diccionarios


#===============================================================================
# Transiciones, Definicion de trabajo y ejecucion. Operaciones de Carga
#===============================================================================


log_cargos=openetl.component.transform.logger(name='Log de cargos')
unique_job = openetl.component.transform.unique()
openetl.transition(csv_in_cargos,pres_cargos,channel_destination='carga_cargos')
openetl.transition(pres_cargos,log_cargos)
openetl.transition(csv_in_cargos,unique_job)
openetl.transition(unique_job,openobject_out4)
job_cargos=openetl.job([csv_in_cargos,unique_job,openobject_out4,log_cargos])
subjob_cargos = openetl.component.transform.subjob(job_cargos)  


unique = openetl.component.transform.unique()
log_dptos=openetl.component.transform.logger(name='Log departamentos')

openetl.transition(csv_in_dptos,unique)
openetl.transition(unique,log_dptos,channel_source='main')
openetl.transition(unique,openobject_out3)
job_dptos=openetl.job([log_dptos,openobject_out3])
subjob_dptos = openetl.component.transform.subjob(job_dptos)  


openetl.transition(openobject_in1,pre_paises, channel_destination='carga_paises')
job_paises = openetl.job([openobject_in1,pre_paises])
subjob_paises = openetl.component.transform.subjob(job_paises)  


openetl.transition(csv_in1,datos_ajustados, channel_destination='modificacion') # Leo datos aplicando preprocesamiento
openetl.transition(csv_in1,openobject_out2) # Direcciones 
openetl.transition(csv_in1,openobject_out1) # Personas


job_ = openetl.job([csv_in1,datos_ajustados,openobject_out2])  # Para poder relacionar direcciones con personas, las direcciones deben estar cargadas
subjob = openetl.component.transform.subjob(job_)              # en el sistema. Las cargo previamente en una subtarea.

job1=openetl.job([subjob_cargos,subjob_dptos,subjob_paises,subjob,csv_in1,datos_ajustados,openobject_out1])
job1.run()
Con esto concluye la parte técnica del post. Creo que OpenETL es una tecnología muy interesante, que permite realizar trabajos de ETL de forma bastante cómoda e intuitiva. También os comento que he echado en falta algo más de documentación técnica sobre OpenETL, ya que he tenido que recurrir al código fuente de muchos componentes, transformaciones, etc. para averiguar que es lo que hacían.
A pesar de ello la línea de aprendizaje de esta tecnología es bastante sencilla una vez que sabes que hay que hacer, y se pueden lograr grandes cosas en poco tiempo.
Para finalizar os comentaré que mi impresión final sobre OpenETL es muy buena. No sólo porque se adapte perfectamente a operaciones ETL sobre OpenERP, sino porque tiene un amplio abanico de conectores (sql, facebook, xmlrpc,csv, gdoc, gcalendar, etc) que permiten usar OpenETL en muchos proyectos con distintas tecnologías.

viernes, 19 de octubre de 2012

Entornos virtuales de python con VirtualEnv

VirtualEnv es un script en python que permite crear entornos virtuales de python (valga la redundancia).


Instalación

sudo easy_install virtualenv

Uso

1.- creo una carpeta donde crearé todos los entornos virtuales de python que necesite:

 mkdir entornos_python

2.- dentro de esta carpeta, puedo crear un entorno llamado entorno1:

 virtualenv --no-site-packages entorno1

--no-site-packages indica que no quiero que se busquen paquetes python fuera de este entorno.

3.- Al meternos dentro del entorno "entorno1", podemos ejecutar:

 source bin/activate

Fijate que ahora al usuario le aparece delante (entorno1) Ahora estamos dentro del entorno :)

4.- Con el siguiente comando salimos fuera del entorno.

deactivate

Esta información la obtuve gracias a este post y a este otro.

martes, 16 de octubre de 2012

Problemas con bazaar y launchpad

Aunque tengo una cuenta de launchpad y uso bazaar habitualmente para descargar código, aún no había subido nada a launchpad, así que no me era necesario tener una clave ssh asociada a mi cuenta.

El problema es que ayer puse a bajar la rama de desarrollo de los addons de openerp, y después de más de 18 horas no había terminado de bajar, mostrándome un mensaje de “Fetching revisions:Inserting stream” que parecía colgado.

bzr branch lp:openobject-addons

Buscando encontré los siguientes post:

En este comentan que la falta de memoria ram puede afectar al bzr. Al parecer el usuario afectado amplió 1 gb de ram el servidor y se solucionó el problema.

En este otro comentan que logándote en launchpad con clave ssh se soluciona el problema. Esta solución ha sido la que me ha funcionado.

Para poder crearte una cuenta de usuario y una clave ssh hay que seguir las siguientes instrucciones.

Después de eso, he tardado 20 minutos en bajar el trunk :)

domingo, 7 de octubre de 2012

Error 36 en Snow Leopard

Esta navidad Apple dejó de dar soporte en actualizaciones de seguridad para leopard, así que decidí actualizar mi mac mini a Snow Leopard.

No todo han sido mejoras, sobretodo en el soporte a samba.

Según apple la nueva implementación de samba falla dando un error 36 si el cliente sólo soporta password en formato plano. Lo curioso es que este error no me está pasando si copio, o leo los archivos desde el terminal, sólo ocurre si realizo las operaciones desde el finder.

Error de Snow Leopard realizando algo que Windows 3.11 hacía sin problemas

Por lo visto este error ocurre desde la versión 10.4. (aunque en mi anterior versión 10.5 no ocurría)

En esta url indican la solución para la versión 10.4.

En mi nueva versión, la 10.6.8 aún no he conseguido solucionarlo. Me refiero a solucionarlo de forma nativa. Sé que existen otros finders que sobrescriben al que viene con snow leopard, pero no creo que la solución adecuada sea dejar de usar el actual. Además no veo lícito comprar extensiones de finder que arreglan este error para una característica que funciona en Windows 3.11.

Otro post relacionados con el problema en mac-forums.

Al parecer el error también aparece en la versión 10.6.2 con volúmenes formateados en FAT, y en la 10.6.3 se corrigió.

A ver si alguien que lea este post puede iluminarme un poco, porque he encontrado cientos de post con soluciones que no me han funcionado. Mientras tanto a seguir usando el terminal.

miércoles, 3 de octubre de 2012

PrettyPhoto, clon de lightbox con jQuery

Efecto PrettyPhoto

Aunque desde hace tiempo uso lightbox para efectos de visualización de fotografía, me ha empezado a dar problemas de incompatibilidades con otros js. Cansado de ajustar el js, decidí buscar una nueva opción en la red que se integre bien con mis aplicaciones.


PrettyPhoto es un js GPLv2 que cubre las mismas funcionalidades de lightbox, pero además permite embeber vídeos, iframes, etc.
La última versión estable se puede descargar desde aquí.


Su uso no podría ser más sencillo, simplemente hay que incluir los js y el css en el head:


 

Y poner en los enlaces:

rel="prettyPhoto"
Se pueden ver distintos ejemplos de uso en su web.

sábado, 29 de septiembre de 2012

Migración de datos entre distintas instancias de OpenERP usando OpenETL

En mi anterior post (o aqui) realicé una introducción a OpenETL. También desarrollé un ejemplo de carga de datos desde una archivo .csv a OpenERP.
En este post voy a profundizar un poco más, realizando una migración de datos de OpenERP a OpenERP en los cuales hay tablas relacionadas.

El escenario

Nuestro entorno de migración constará de las siguientes características:
  • BD_inicial contiene los datos que queremos migrar. Los datos serán los clientes y proveedores con sus direcciones.
  • No todos los clientes o proveedores tienen una dirección asociada, por lo que hay que controlar la excepción.
  • Para simplificar el ejemplo voy a migrar sólo el contenido de los campos name, title y partner_id, siendo title el campo que contiene la relación con la tabla res_partner_title y partner_id el campo relacionado con la tabla res_partner.
Relación simplificada de relaciones de objetos res_partner_address, res_partner_title y res_partner.

A tener en cuenta

Cuando migras un contenido desde OpenERP a un archivo .csv el sistema suele funcionar sin complicaciones. Sin embargo cuando se realiza la migración de OpenERP a OpenERP es fácil obtener excepciones tal como

File "/usr/lib/python2.6/xmlrpclib.py", line 838, in close
    raise Fault(**self._stack[0])
xmlrpclib.Fault: 
Esto ocurre porque OpenERP al leer un campo sin valor le asigna por defecto un booleano inicializado a False.
En el ejemplo data_map.py después de leer los valores de ejemplo desde el .csv el autor realiza una transformación de los mismos antes de mostrarlos en el log. Basándose en ese ejemplo, es fácil inicializar los campos con valores adecuados:
def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:            
            if d["name"] == False:
                d["name"] = ""
            cdict[d['id']] = d
    return {'resultado':cdict}  
Otra cosa que también puede producir muchos quebraderos de cabeza es que en los campos relacionados no se va a poner el id de la tupla relacionada, sino el valor de la misma. Nuestra función quedaría así:
def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:
            if d["title"] == False: # Es una relacion, ej: 'title': [5, 'Sir'] , con res_partner_title
                d["title"] = '' # Si quiero dejarlo sin valor, le dejo las comillas
            else:
                d["title"] = d["title"][1]  # No se coge el 0, que es el id, sino el valor. El id se ajusta automatico :)
            if d["name"] == False:
                d["name"] = ""
                
            if d["partner_id"] == False:
                d["partner_id"] = ""
            else:    
                d["partner_id"] = d["partner_id"][1]
                            
            cdict[d['id']] = d
    return {'resultado':cdict}     

El resultado

El código completo, con conectores, componentes, transiciones, etc. se muestra a continuación. Nótese que la función de procesamiento es llamada desde una transición openetl.component.transform.map(map_keys,preprocess), en la que se pasa también un parámetro map. Hay más ejemplos parecidos en data_map.py y m2m_into_oo.py.
#!/usr/bin/python

import sys
sys.path.append('..')

import openetl
from openetl import transformer

# Conectores
ooconnector_in = openetl.connector.openobject_connector('http://localhost:8069', 'BD_inicial', 'admin', 'admin', con_type='xmlrpc')
ooconnector_out = openetl.connector.openobject_connector('http://localhost:8069', 'BD_final', 'admin', 'admin', con_type='xmlrpc')

# Componentes
openobject_in1 = openetl.component.input.openobject_in(
                 ooconnector_in,'res.partner.address',
                 fields=['id','title','name','partner_id'],
                 )


openobject_in2 = openetl.component.input.openobject_in(
                 ooconnector_in,'res.partner',
                 fields=['id','name'],
                 )



openobject_out1 = openetl.component.output.openobject_out(
     ooconnector_out,
     'res.partner.address',
     {'name':'name','title':'title','partner_id':'partner_id'}
    )

openobject_out2 = openetl.component.output.openobject_out(
     ooconnector_out,
     'res.partner',
     {'name':'name'}
    )

log=openetl.component.transform.logger(name='Recien leido:Read Partner File ')

# Soporte transformaciones

map_keys = {'main': {
    'name': "resultado[main['id']]['name']",
    'title': "resultado[main['id']]['title']",
    'partner_id': "resultado[main['id']]['partner_id']",
}}



def preprocess(self, channels):
    cdict = {}
    for trans in channels['modificacion']:
        for d in trans:
            if d["title"] == False: # Es una relacion, ej: 'title': [5, 'Sir'] , con res_partner_title
                d["title"] = '' # Si quiero dejarlo sin valor, le dejo las comillas
            else:
                d["title"] = d["title"][1]  # No se coge el 0, que es el id, sino el valor. El id se ajusta automatico :)
            if d["name"] == False:
                d["name"] = ""
                
            if d["partner_id"] == False:
                d["partner_id"] = ""
            else:    
                d["partner_id"] = d["partner_id"][1]
                            
            cdict[d['id']] = d
    return {'resultado':cdict}               

map=openetl.component.transform.map(map_keys,preprocess)


# Transiciones
tran1=openetl.transition(openobject_in1,map, channel_destination='modificacion')
tran3=openetl.transition(openobject_in1,log)

tran_res_partner01=openetl.transition(openobject_in2, openobject_out2)

tran4=openetl.transition(openobject_in1, map)
tran4=openetl.transition(map, openobject_out1)


# Definicion de trabajo y ejecucion
job1=openetl.job([openobject_in1,map,openobject_out1,openobject_in2,openobject_out2])
job1.run()

Este código funciona y realiza la migración de datos sin ningún problema siempre que en las tablas relacionadas no haya ningún dato con igual campo “valor relacionado” repetido. ¿Y qué pasa si el “valor relacionado” sí está repetido? Lo que ocurre en este caso es que el sistema creará la relación con la tupla con id más pequeño. Para corregir esta situación bastaría con añadir alguna condición más a la función preprocess, ayudarnos de alguna otra función en python auxiliar, etc. Si se diera ese caso los ejemplos sql_in_example.py, csv_diff_example.py, join_example.py, podrían servir como base en función del tratamiento que quisiéramos hacer.



miércoles, 19 de septiembre de 2012

Uso de Yield en Python

Se usa para funciones generadoras. Funciona como un return, pero la próxima vez que se llame el código continúa justo debajo de yield.

Código de ejemplo:

 def contador_yield(max2):
     print "Inicio contador_yield"
     n = 0
     while (n < max2):
         print "Deltro del while de contador_yield"
         yield n
         n +=1    
         print "Sigo dentro del metodo"
         print "Sigo dentro del metodo22"
         print "Sigo dentro del metodo233"
         
 c = contador_yield(3)

 print c.next()
 print "Ya he realizado la llamada"
 print c.next()
 print c.next()
 print c.next()

Salida:

Inicio contador_yield
 Deltro del while de contador_yield
 0
 Ya he realizado la llamada
 Sigo dentro del metodo
 Sigo dentro del metodo22
 Sigo dentro del metodo233
 Deltro del while de contador_yield
 1
 Sigo dentro del metodo
 Sigo dentro del metodo22
 Sigo dentro del metodo233
 Deltro del while de contador_yield
 2
 Sigo dentro del metodo
 Sigo dentro del metodo22
 Sigo dentro del metodo233
 Traceback (most recent call last):
   File "/home/carlos/workspace/test_yield.py", line 19, in 
     print c.next()
 StopIteration

Documentación:

Blog Python Manía. Uso de yield

Python Doc#yield

jueves, 2 de agosto de 2012

Carga de datos en OpenERP usando OpenETL

Introducción:

OpenETL es una librería en python de migración de datos de OpenERP S.A. Esta herramienta nos permite realizar todas las operaciones típicas de los ETL (extracción, transformación y carga) con el valor añadido de estar muy bien integrada con OpenERP. La página web de la librería es https://launchpad.net/openetl También existe una interfaz gráfica empaquetada como módulo (etl_interface) para OpenERP. Este módulo está en los extra-addons. Aunque desde la interfaz es más sencillo el manejo, este post se centrará en el uso de la librería python.

Instalación:

Para bajar la librería es necesario tener instalado bazaar http://es.wikipedia.org/wiki/Bazaar_(software). El comando concreto:
bzr branch lp:openetl
Una vez bajada la rama de openerp hay que copiar la carpeta openetl/lib/openetl a la carpeta de librerías de tu sistema. En mi caso /usr/lib/python2.6/ . Aunque en mi eclipse con PyDev ya tengo configurada esa ruta, Eclipse Indigo parece no pillar la librería a la primera. Para refrescar las librerías accesibles en eclipse hay que entrar en PyDev\Interpreter - Python pulsar sobre Restore Defaults (si ya teníamos configurado el sistema) y después sobre Apply.

Funcionamiento:

La librería se divide en trabajos, componentes, conectores, y transiciones.
  • Jobs: Procesos que se pueden ejecutar, pausar y parar.
  • Components: Componentes de entrada, salida y transformación. Nos permiten obtener datos y cargar datos en sistemas externos. Los componentes de transformación serán los que adapten los datos antes de realizar la carga final.
  • Connectors: Los conectores son los que definen las conexiones con los sistemas externos. Son usados por los componentes. La versión actual de la librería tiene conectores para tratar archivos locales, Openobjects, distintas bases de datos (postgres, mysql, oracle), urls (http, ftp, https, gopher), servicios webs xmlrpc, sugarCRM, distintos servicios de google (gdocs, gcalendar, gblog) y facebook.
  • Transitions: Las transiciones son el flujo por el que pasan los datos entre los distintos componentes.
El programador debe definir tantos conectores de entrada como salida necesite, al menos un componente por cada conector, y una transición mínima para pasar los datos de un componente a otro. Al estar los conectores “enlazados” con los componentes, al escribir los datos en el componente se escriben en el sistema externo.

Ejemplo:

Tenemos un sistema OrangeHRM instalado en la empresa con las fichas de recursos humanos que queremos migrar a OpenERP. El personal de recursos humanos ha exportado los datos a formato .csv y nos pide que realicemos la carga de datos a OpenERP. Lo que tenemos que hacer es crear dos conectores, uno se conectará en local contra el archivo .csv y el otro de tipo XMLRPC se conectará con OpenERP. A continuación definiremos los componentes que almacenarán la información, crearemos las transiciones (una para escribir los datos en el componente final y otra para ordenar los datos) que se ejecutarán y por último lanzaremos la tarea. En el componente de OpenObject definiremos el mapeo de los campos del csv a los campos del objeto erp. Las transiciones se ejecutan de forma secuencial según se hayan definido en el .py.

El código fuente de este ejemplo:


import sys
sys.path.append('..')

import openetl
 
# Conectores
fileconnector_orange=openetl.connector.localfile('/home/carlos/Escritorio/DatosRRHHOrangeHRM.csv')

ooconnector = openetl.connector.openobject_connector('http://localhost:8069', 'testProject', 'admin', 'admin', con_type='xmlrpc')

# Componentes
csv_in1= openetl.component.input.csv_in(fileconnector_orange,name='Datos de Orange')

oo_out_employee = openetl.component.output.openobject_out(
     ooconnector,
     'hr.employee',
     {'name':'firstName'}
    )


sort1=openetl.component.transform.sort('firstName')

# Transiciones

tran1=openetl.transition(csv_in1,sort1)

tran2=openetl.transition(sort1, oo_out_employee)

# Definicion de trabajo y ejecucion
job1=openetl.job([csv_in1,sort1,oo_out_employee])
job1.run()
Para facilitar el ejemplo, he simplificado el número de campos a cargar desde el .csv, los campos con tablas relacionadas, etc. Pero la carga se puede complicar tanto como sea necesaria para que todos los datos estén migrados correctamente. En la carpeta openetl\examples hay ejemplos de todas las cosas que nos harán falta en el proceso de migrado de datos, pasando desde ejemplos con entradas y salidas múltiples (csv_in_out.py) hasta carga de datos con tablas relacionadas (m2m_into_oo.py). Este ejemplo es bastante interesante, puesto que lee usuarios y grupos de archivos csv y los carga directamente en OpenERP. También hay que destacar los ejemplos de migración de datos desde sugarcrm, facebook, gcalendar, etc.

lunes, 9 de julio de 2012

Instalando psyco para corregir warning y mejorar velocidad de ejecución python

Hoy al arrancar un proyecto (OpenERP) desde eclipse en modo debug, el sistema me ha mostrado un warning:

warning: psyco not available for speedups (the debugger will still work correctly, but a bit slower)

Buscando por internet he encontrado que psyco es un proyecto alojado en
http://psyco.sourceforge.net/psycoguide/sources.html
Por lo visto este módulo lo que hace es acelerar la ejecución del código python. No es necesario para funcionar, pero sí recomendable.

Para instalarlo en windows encontré la siguiente página (hay archivos ya compilados, pero no los he probado porque mi instalación era ubuntu 10.04).

http://www.voidspace.org.uk/python/modules.shtml#psyco

Para la instalación en linux se puede bajar el código del svn del proyecto:

svn co http://codespeak.net/svn/psyco/dist/ psyco-dist


Y realizar el típico install:
sudo python setup.py install


Según la documentación del proyecto, es necesario tener en el sistema el módulo python-dev
sudo apt-get install python-dev

En mi máquina yo tenía instalada la versión 2.6 de python y todo ha ido bien.

Enlaces recomendados:

- Página de los fuentes del proyecto:
http://psyco.sourceforge.net/psycoguide/sources.html

- Página donde encontré un poco de luz sobre el tema y los enlaces a la versión windows: http://www.insomnihack.com/?p=412
Related Posts Plugin for WordPress, Blogger...