Sie sind auf Seite 1von 176

Ciabay S. A.

Ciabay S. A.
Informtica
Fadel Damen Schreiner

Openerp - Entrenamiento Tecnico (desarrollo)


Hernandarias, 15/03/2012

Table of Contents
1 INTRODUCCIN .................................................................................................V
2 BAJAR Y INSTALAR OPENERP 6.1 EN UBUNTU 11.10..............................VI
2.1
2.2
2.3
2.4
2.5
2.6
2.7

ACTUALIZACIN DE LOS PAQUETES DE UBUNTU......................................................................... VI


CREAR USUARIO OPENERP EN EL SISTEMA OPERATIVO................................................................. VI
INSTALACIN DE LA BASE DE DATOS POSTGRESQL ...................................................................... VI
INSTALANDO LAS BIBLIOTECAS PYTHON PARA CORRER OPENERP.................................................... VII
INSTALACIN DEL BAZAAR................................................................................................... VII
BAJANDO LOS FUENTES....................................................................................................... VII
CONFIGURACIN DEL OPENERP............................................................................................. VIII
2.7.1 Camino del servidor..............................................................................................ix
2.7.2 Configuracin de aceso al PostgreSQL.................................................................ix
2.7.3 Configuraciones de las carpetas de los addons, addons-extras e web. (importante
no dejar espacio entre los comas).........................................................................ix
2.7.4 Archivos de logs.....................................................................................................ix
2.8 CONFIGURACIN DEL DAEMON (CORRER COMO SERVICIO)............................................................ IX
2.9 BAJANDO OPENERP VERSIN DE DESARROLLO............................................................................ X

3 INSTALACIN DE MODULOS Y CREACIN DE UNA BASE DE DATOS.


...............................................................................................................................XII
4 INSTALACIN DE LOS PLUGINS DE GEDIT PARA DESARROLLO
..........................................................................................................................XVIII
5 ENTORNO DE DESARROLLO CON ECLIPSE (APTANA).......................XX
6 INSTALACIN DEL PGADMIN 3...........................................................XXVIII
7 OPENOBJECT COMO RAD.....................................................................XXXIV
7.1 MODELO, VISTA, CONTROLADOR (MVC)........................................................................ XXXVI
7.2 COMPOSICIN DE UN MODULO....................................................................................... XXXVIII
7.3 ESTRUTURA DE CARPETAS DE UN MODULO......................................................................... XXXIX
7.3.1 __init__.py.............................................................................................................xl
7.3.2 __terp__.py o __openerp__.py..............................................................................xl
7.4 MENUS Y ACCIONES (ACTIONS).......................................................................................... XLIII
7.4.1 Menu..................................................................................................................xliii
7.4.2 Declaracin de una accin ...............................................................................xliii
7.5 SERVICIO DE OBJETOS ORM........................................................................................... XLV
7.5.1 Tipos de campos del ORM..................................................................................xlvi
7.5.2 Campos especiales y nombres reservados........................................................xlviii
7.5.3 Metodos del ORM con los objetos osv.osv........................................................xlviii
7.5.4 Mecanismo de la herencia.......................................................................................l
7.6 CONSTRUYENDO LA INTERFACE DEL USUARIO............................................................................ LI
7.6.1 Estructura comun del XML....................................................................................li
7.6.2 La sintaxis los archivos CSV.................................................................................lii
7.6.3 Vistas y Herencias de vista....................................................................................lii

.
7.7 DOMINIOS EN CAMPOS DE LAS CLASES.................................................................................... LX
7.8 DEPURACIN DE CDIGO CON APTANA.................................................................................. LXII
7.9 UTILIZACIN DE CAMPOS TIPO FUNCIN Y OBJETOS DE ORM................................................... LXV
7.10 EVENTOS EN LAS VISTAS................................................................................................. LXVI
7.11 CONTRAINTS............................................................................................................... LXVIII
7.12 HERENCIA EN METODOS.................................................................................................. LXIX
7.13 ATRIBUYENDO VALOR DEFAULT A CAMPOS........................................................................... LXX
7.14 MANEJANDO COLORES EN EL GRID................................................................................... LXXI
7.14.1 Uso de caracteres especiales en las vistas........................................................lxxi
7.15 VISTA TIPO CALENDARIO. ............................................................................................. LXXVI
7.16 VISTAS DEL TIPO GANTT.............................................................................................. LXXVII
7.17 VISTAS DEL TIPO GRAFICOS (GRAPHS)............................................................................ LXXVIII
7.18 VISTAS TIPO SEARCH (BUSQUEDA)................................................................................... LXXIX

8 RESOLUCIN DE LOS EJERCICIOS.................................................LXXXIII

List of Figures
Ilustracin 1: Complementos Gedit (recortes)..........................................................xviii
Ilustracin 2: Gedit code completation.......................................................................xix
Ilustracin 3: Aptana IDE.............................................................................................xx

List of Tables

Tabla 1: Actualizacin de los paquetes de ubuntu.......................................................vi


Tabla 2: Crear usuario openerp en el sistema operativo..............................................vi
Tabla 3: Instalacin de la base de datos postgresql.....................................................vi
Tabla 4: Crear usuario para la base de datos...............................................................vi
Tabla 5: Instalando las bibliotecas python para correr openerp.................................vii
Tabla 6: Instalacin del bazaar...................................................................................vii
Tabla 7: Estructura necesaria para bajar los fuentes..................................................viii
Tabla 8: Bajando script que baja todos los repositorios de OpenERP......................viii
Tabla 9: Bajando Fuentes..........................................................................................viii

Appendices

1. Introduccin

1 Introduccin
En este manual queremos mostrar como desarrollar un modulo para openerp que
abran ja una gran parte del conocimiento necesario para administrar nuevos desarrollos. Todo va estar basado en la versin de openerp 6.1 el modulo que vamos a desarrollar va llamarse openacademy que ser un sistema para el control de los cursos ministrados en openERP con sus participantes.

2. Bajar y instalar openerp 6.1 en ubuntu 11.10

2 Bajar y instalar openerp 6.1 en ubuntu 11.10


Por mas que queramos tener todo listo para utilizar y desarrollar en openerp para
windows siempre lo mejor va ser tener todo bajo linux, ya que es su principal fuente
de desarrollo y todo es probado y funciona mejor con el.
2.1

Actualizacin de los paquetes de ubuntu

Inicialmente vamos a actualizar los repositorios de nuestro linux, para tener todos
los paquetes necesarios para su utilizacin.
Tabla 1: Actualizacin de los paquetes de ubuntu
$ sudo apt-get update
$ sudo apt-get dist-upgrade

2.2

Crear usuario openerp en el sistema operativo

Tendremos la necesidad de crear un usuario que va correr el openerp server en la


maquina ya que openerp no nos deja correr el server con el usuario root.
Para eso vamos a crear un usuario en nuestro linux con el nombre openerp que
tambin el grupo sea openerp su directorio home va ser el /opt/openerp.
Tabla 2: Crear usuario openerp en el sistema operativo
$ sudo adduser --system --home=/opt/openerp --group openerp

2.3

Instalacin de la base de datos postgresql

Para instalar el postgresql utilizaremos el gerenciador de paquete de del propio


ubuntu utilizando el comando:
Tabla 3: Instalacin de la base de datos postgresql
$ sudo apt-get install postgresql

Luego sera necesario crear el usuario que va correr las nuevas bases de datos de
openerp, que son creadas automaticamente por el openerp server.
Tabla 4: Crear usuario para la base de datos
$ sudo su postgres
createuser --createdb --username postgres --no-createrole --pwprompt openerp
Enter password for new role: ******
Enter it again: ******
Shall the new role be a superuser? (y/n) y

Lluego utilizamos el comando exit para salir del usuario postgres


$ exit

2. Bajar y instalar openerp 6.1 en ubuntu 11.10


2.4

Instalando las bibliotecas python para correr openerp

Para que corra el openerp 6.1 en el ubuntu es necesario instalar algunas bibliotecas
de python que lo instalamos con el comando abajo:
Tabla 5: Instalando las bibliotecas python para correr openerp
$ sudo apt-get install python python-psycopg2 python-reportlab python-egenix-mxdatetime python-tz python-pychart python-mako \
python-pydot python-lxml python-vobject python-yaml python-dateutil python-pychart python-webdav python-cherrypy3 python-formencode \
python-pybabel python-simplejson python-pyparsing python-werkzeug python-openid

Los modulos en amarillo son necesarios apenas para la instalacin de openerp en la


versin 6.1 para las versiones anteriores no son necesarios.
2.5

Instalacin del bazaar

Bazaar es una herramienta para control de versiones utilizada por la cannica creadora de ubuntu, es una herramienta poderosa en que se utiliza para mantener los codigos fuentes de openerp como tambien es utilizada por la comunidad para mantener
tambin los cdigos de los mdulos extra-oficiales. Es una herramienta fundamental
para que trabajemos con openerp.
Para instalar utilzamos tambien el repositorio de ubuntu.
Tabla 6: Instalacin del bazaar
$ sudo apt-get install bzr

2.6

Bajando los fuentes.

Openerp esta dividido en varias partes, como descrito abajo. Y son bajados en varios directorios separados.
addons Carpeta con los mdulos core de OpenERP

addons-extra Carpeta con los mdulos extra de OpenERP

addons-community Carpeta con los mdulos de la comunidad del OpenERP

br Carpeta con los mdulos da localizacin Brasilera

server - Servidor OpenERP

client - Cliente GTK do OpenERP

web - Cliente Web do OpenERP

Si bien vemos que esta dividido en varias partes, pero vale resaltar que tenemos
una carpeta arriba que es de la localizacin brasilera, esto esta asi porque fue desarrollado un script por ellos para bajar todos estos componentes, si no lo utilizamos tendramos que bajar uno a uno y como no lo vamos simplemente a utilizar no nos causa
ningn problema y nos facilita para bajar e instalar todos los componentes.
Vamos crear la estructura necesario para bajar los fuentes.
7

2. Bajar y instalar openerp 6.1 en ubuntu 11.10


Para que podamos entrar com usuario openerp y cuando lo creamos con adduser
por ser usuario de sistema el no crea el los archivos de sistema para un usuario comun
como tampoco crea el acceso a shell para el mismo, para eso vamos editar el
archivo /etc/passwd y poner en el usuario openerp /bin/bash en cambio a /bin/false.
Para eso vamos utilizar el editor pico.
Tabla 7: Estructura necesaria para bajar los fuentes
$ sudo pico /etc/passwd
buscamos el usuario openerp y cambiamos al final de la linea /bin/false para /bin/bash
$ sudo su - openerp
$ cd /opt/openerp
$ mkdir v6.1
$ cd v6.1

Bajamos del bazaar el script que baja todos los fuentes.


Tabla 8: Bajando script que baja todos los repositorios de OpenERP
$ bzr branch lp:~openerp-brazil-team/openerp/openerp-br

Nos baja en la carpeta openerp-br el script para bajar todos los funtes y luego digitamos.
Tabla 9: Bajando Fuentes
$ cd openerp-br
$ ./bzr_set.py

Esto va tardar bastante. Ahora tenemos todas las carpetas que mencionamos arriba,
web -addons client etc.

2.7

Configuracin del openerp

En este capitulo vamos a configurar el openerp para que lea todos los archivos necesarios y tambin para que arranque en la inicializacin de nuestro sistema ubuntu.
En primer lugar vamos crear los archivos para que el openerp pueda crear los logs.
$ exit
$ cd /var/log
$ sudo mkdir openerp
$ sudo chown openerp:openerp openerp

As una vez que tenemos estos directorios ya creados vamos por la configuracin
de openerp.
El openerp necesita que tengamos un archivo conf que contenga los usuarios y
contrasea del administrador de la base de datos. Un archivo modelo basico se puede
encontrar en
/opt/openerp/v6.1/openerp-br/server/install/openerp-server.conf

2. Bajar y instalar openerp 6.1 en ubuntu 11.10


Como vamos a poner el openERP como servicio en el linux, es importante copiar
el archivo openerp-server.conf para la carpeta /etc/
$ sudo cp /opt/openerp/v6.1/openerp-br/server/install/openerp-server.conf /etc/openerp-server.conf

Para entrar en el archivo para editar sus configuraciones utilizamos el mcedit si no


lo tenemos instalado podremos instalar con:
$ sudo apt-get install mc

Y para editar el archivo.


$ sudo mcedit /etc/openerp-server.conf

En el archivo de configuracin tenemos que tener en cuenta los parametros abajo


mencionados puede que no exista los mismos dentro de sus configuraciones si es asi
agregalos.
2.7.1 Camino del servidor
root_path = /opt/openerp/v6.1/openerp-br/server/bin

2.7.2 Configuracin de aceso al PostgreSQL


; Database settings
db_user = openerp
db_password = <contrasea creada en el primer capitulo>

2.7.3 Configuraciones de las carpetas de los addons, addons-extras e web.


(importante no dejar espacio entre los comas)
addons_path
=
/opt/openerp/v6.1/openerp-br/addons,/opt/openerp/v6.1/openerp-br/addons-extra,/opt/openerp/v6.1/openerpbr/web,/opt/openerp/v6.1/openerp-br/web/addons

2.7.4 Archivos de logs.


; Log settings
logfile = /var/log/openerp/openerp-server.log

Archivo completo openerp-server.conf

2.8

Configuracin del daemon (correr como servicio)

Configurar el openERP para que corra como daemon es para que corra el openerp
al iniciar la maquina automaticamente, y quede como servicio.
Copiamos el script para sus lugares correctos.
$ sudo cp /opt/openerp/v6.1/openerp-br/server/debian/openerp.init /etc/init.d/openerp-server

Damos permisiones de ejecucin al script.

2. Bajar y instalar openerp 6.1 en ubuntu 11.10


$ sudo chmod +x /etc/init.d/openerp-server

Editamos el archivo para poner los caminos correctos de los ejecutables.


$ sudo mcedit /etc/init.d/openerp-server

Editamos estos parametros en el archivo.


DAEMON=/opt/openerp/v6.1/openerp-br/server/openerp-server
CONFIG=/etc/openerp-server.conf
LOGFILE=/var/log/openerp/openerp-server.log

Todavia en el archivo del /etc/init.d/openerp-server tenemos que cambiar el pid file


de local vamos encontrar dentro del archivo en varias lineas el comando estar-stopdaemon en el parametro pidfile cambiamos de:
start-stop-daemon --stop --quiet --pidfile /var/run/${NAME}.pid
Para:
start-stop-daemon --stop --quiet --pidfile /var/run/openerp/${NAME}.pid
Ahora antes de probamos si esta todo ok las configuraciones tenemos que crear el
diretorio para el pid y luego probamos levantar el servidor.

$ sudo mkdir /var/run/openerp


$ sudo chown openerp.openerp /var/run/openerp
$ sudo /etc/init.d/openerp-server start

Y verificamos el archivo de log para saber si corre todo bien.


$ sudo cat /var/log/openerp/openerp-server.log

Si todo corre bien, podremos ya poner para ejecutar en el inicio del sistema.
$ cd /etc/init.d
$ sudo update-rc.d openerp-server defaults

2.9

Bajando openerp versin de desarrollo.

Tenemos un script utilizado para bajar el codigo fuente de openerp de diversas versiones, incluso la versin de dearrollo que podriamos ver las mejorias en la trama
trunk.
Yo personalmente me gusta usar esta versin que normalmente esta mas estable, y
queda facil correr el openerp-server para el desarrollo de modulos. Esto se da ya que
al desarrollar modulos siempre es necesario bajar y volver a subir openerp-server para
volver a cargar el modulo desarrollado.

10

$ sudo su - openerp
$ mkdir /opt/openerp/dev
$ cd /opt/openerp/dev
$ bzr cat -d lp:~openerp-dev/openerp-tools/trunk setup.sh | sh

Al terminar de bajar este escript nos aparecer al terminal el siguiente mensaje.


To setup your development enviroment and download the sources, type:
-for OpenERP v6.0:
make init-v60
-for OpenERP v6.1:
make init-v61
-for OpenERP trunk:
make init-trunk
for OpenERP latest stable and trunk:
make init

De esta forma podremos bajar e ejecutar varias versiones de openerp por ejemplo
vamos bajar la versin 6.1.

$ make init-v61

11

3. Instalacin de modulos y creacin de una base de datos.

3 Instalacin de modulos y creacin de una base de datos.


Tenemos todo instalado con nuestro servidor corriendo que es una perfecta instalacin
para como servidor de produccin entramos en el navegador con la direccin ip de la
maquina seguido de :8069 que es el puerto estandart de openerp y nos econtramos con
la pantalla abajo para poder crear nuestra primer base de datos utilizando la opcin en
azul Manager database.

Nos pedira la contasea maestra ya completada como admin que es el estandart y


en nombre de la base de dato que le pusimos primer_banco_full luego si queremos
que sean cargados los datos de demostracin de OpenERP el lenguaje principal y una
contrasea para el usuario admin de esta base de datos.

12

3. Instalacin de modulos y creacin de una base de datos.


Despues de un rato openerp ya va entrar directamente a la aplicacin dejando la
pantalla bajo disponible para que tengamos la oportunidad de instalar los modulos necesarios.

Para ver lo grandioso que es el openERP vamos instalar todos los modulos para eso
solamente tendremos que clicar en instalar en cada uno de los arriba mostrados. Pero
Porque al clicar por em crm automaticamente OpenERP nos brinda ya una pantalla,
con algunas listas y graficos y ya no tenemos disponibles los otros modulos para istalar ? Esto es normal y para mostrar su funcionamiento cada modulo tenemos una herramiente espetacular, que se llama dashboard que puede ser customizada por el usuario y muestra los dados para seguimiento del modulo.

13

3. Instalacin de modulos y creacin de una base de datos.


Para instalar los otros modulos en OpenERP nos vamos al menu superior Setting y
en el menu lateral tenemos modulos:

Al clicar en mdulos tendremos disponibles todos los modulos oficiales para su


instalacin.
Notamos que al ir clicando en instalar cada modulo openerp toma su tiempo para ir
instalando cada modulo llamando las configuraciones para cada uno de ellos y nos
abre siempre el dashboard del modulo instalado, si queremos marcar varios modulos y
luego instalar todos juntos tenemos que cambiar el modo vista de los modulos. Utilizamos los botones en el lado superior derecho como mostrado abajo:

Destacado en azul tenemos las distintas vistas que el modulo de modulos posee,
como deberas saber el modulo que instala mdulos en openerp es un modulo del sistema como cualquier otro. Veamos los tipos que tenemos disponible para este modulo
de modulos.

Vista kanban que puede mostrar iconos, tarjetas e varios otros tipos de dados
como veremos luego.

14

3. Instalacin de modulos y creacin de una base de datos.


Vista Lista (tree) que nos mostra un grid con un listado con los modulos existentes
con los mismos modulos que ya vimos con la vista kanban pero en un listado que nos
da la posibilidad e tratar varios molulos a la vez por exemplo instalar varios a la vez.

En esta Vista tipo lista, todavia tenemos la posibilidad de ir instalando uno a uno
los modulos utilizando el icono amarillo a lado de la columna ESTADO pero al mostrar la ventana de confirmacin de la instalacin podremos dar cancelar y el ESTADO
va quedar en azul escrito A ser instado vamos clicando uno a uno los modulos y al
ultimo damos actualizar.
Luego de un rato nos aparece otra ventana para configurar los modulos clicamos en
START CONFIGURATIONS
Y en la barra superior ya nos muestra los menus de los modulos instalado.

Al clicar en cada uno de estos modulos nos mostrara el dashboard de cada modulos.
Las otras 2 vistas veremos al momento de trabajar con desarrollo que son las vistas
formulario y paginas.
Es importante salientar que para que quede fcil de empezar a utilizar openerp muchas configuraciones quedan como estandart asumiento una optima forma de trabajo,
pero tenemos la posibilidad de definir como va trabajar openerp en cada modulo. Para
esto vamos volver al modulo setting, en este modulo que es de responsabilidade del
administrador del sistema el dashboard nos muestra una pantalla con la opcin de configurar el sistema en general y a su lado una lista de usuario ya creados en el sistema.

15

3. Instalacin de modulos y creacin de una base de datos.

Podremos ir seleccionando las opciones que queramos configurar que nos va mostrar al final un wizard para las configuraciones.

Tenemos para cada modulo algunas configuraciones posibles.


Tenemos 2 opciones, si clicamos en el link subrajado por ejemplo nos abrira el wizard correspondiente para la configuracin o si ya lo sabemos como funcionan podemos tambien decir que ya esta configurado marcando la casilla a lado que le dice al
sistema que ya esta configurado.
Como administrador por ejemplo podriamos definir las preferencias default de los
usuarios creados. Clicando en define default user preferences nos tirara un wizard
para configuracin. Como vemos abajo.

16

3. Instalacin de modulos y creacin de una base de datos.


Tenemos en esta configuracin la opcin de que todos lo usuarios utilizen una interface simplificada, por ejemplo esto si dejado en simplificado dice al openerp que
oculte varios campos menos importantes del sistema para el usuario, e en otro momento definimos cuales. Luego tenemos la opcin de lenguaje estandart y su timezone
y no menos importante que al usuario le mostre los TIPS que son explicativos que el
openerp puede dejar al usuario en cada modulo y deshabilitados por el mismo cuando
desee.
Damos aplicar y ya vemos que el indicador nos muestra que tenemos 10% del sistema configurado.

17

4. Instalacin de los plugins de gedit para desarrollo

4 Instalacin de los plugins de gedit para desarrollo


Para bajar de bazaar el codigo fuente del xml para agregar al plugin de gedit es este
comando:
mkdir /tmp; cd /tmp
bzr branch lp:openobject-gedit

Luego de bajar tendramos un directorio llamado openobject-gedit, y a dentro tendramos los archivos: js.xml python.xml README.txt xml.xml, para que funcione en
ubuntu 10.04 a 11.04 tendramos que instalar inicialmente el soporte y multiplos plugins de gedit con el comando :
sudo apt-get install gedit-plugins

Lluego de haber instalado tendramos que tener en nuestro gedit el plugins snippets
que en espaol se llama recortes en portugues se llama trechos. Para que todo funcione es necesario que en la carpeta de nuestro usuario linux agregar los archivos xml
bajado de bazaar en el diretorio /tmp con el comando.
$ sudo su
#mkdir ~/.gnome2/gedit/snippets
#cp /tmp/openobject-gedit/*.xml ~/.gnome2/gedit/snippets

Para probar tendramos que primeramente habilitar el plugin en el menu Editar


Preferencias y en la ventana seleccionar la pestaa complementos y seleccionar recortes.

Ilustracin 1: Complementos Gedit (recortes)

18

4. Instalacin de los plugins de gedit para desarrollo

En linux no fue posible hacer la importacin de los xml, lluego abajo haremos la
instalacin del gedit para windows Si WINDOWS. Para instalar el Gedit en windows
bajamos

el

mismo

de

esta

direc-

cin:http://ftp.gnome.org/pub/gnome/binaries/win32/gedit/ Bajamos de ai la ultima


version que haora es la 2.30.
Al instalar con el famoso proceso de next next finish, asi ya instalado solo necesitamos habilitar el plugin como en linux en el menu Editar Preferencias y en la ventana seleccionar la pestaa complementos y seleccionar recortes, luego tendramos
que entrar en configurar el complemento y con el botn de importar recortes podremos importar los 3 archivos xml.
En los 2 casos para probar si el plugin funciona es necesario que el gedit sepa que
tipo de archivos estamos trabajando ay 2 formas de hacerlo una es al crear un archivo
nuevo ya en blanco hacemos un guardar como y ponemos su extensin, siendo que al
guardar como .py el plugin va levantar para python y si guardamos el archivo en xml,
va trabajar con archivos de xml de openerp, la otra forma es entrando en el menu Ver
modo resaltado scripts python.
Luego digiramos cla en el gedit y damos un CRTL+barra_de_espacio y nos aparecera el code completation a selecionar como vemos abajo.

Ilustracin 2: Gedit code completation

19

5. Entorno de desarrollo con eclipse (aptana)

5 Entorno de desarrollo con eclipse (aptana)


Tenemos 2 opciones para la instalacin del entorno de desarrollo de eclipse, utilizar
el eclipse genrico o utilizar un eclipse modificado con algunas configuraciones ya
predefinidas para el desarrollo de python.
Con eso vamos utilizar el aptana que tiene una versin gpl 3. El entorno lo podemos descargar de la siguiente direccin: http://www.aptana.com/products/studio2/download . Tenemos la posibilidad de bajar completo o como plugin de eclipse, como
queremos facilitar la instalacin bajamos la versin Standalone.
Una vez descargada, descomprimir a un directorio concreto y ya lo tenemos preparado para funcionar. Para ponerlo en marcha, acceder al directorio y ejecutar el script.
Pueda que no tengamos en el sistema instalado el java que es una de las dependencias de este programa es la instalacin del java en ubuntu, utilizamos el comando:
$sudo apt-get purge openjdk*
$sudo apt-add-repository ppa:flexiondotorg/java
$sudo apt-get update
$ sudo apt-get install sun-java6-jdk
$ wget -O xulrunner.deb http://launchpadlibrarian.net/70321863/xulrunner-1.9.2_1.9.2.17%2Bbuild3%2Bnobinonly-0ubuntu1_i386.deb
$ sudo dpkg -i xulrunner.deb

Ilustracin 3: Aptana IDE

El entorno es muy parecido a Eclipse con una zona central para desarrollo, un explorador de ficheros a la izquierda y la posibilidad de elegir diferentes perspectivas segn el entorno de programacin que necesitemos. El siguiente paso consiste en activar
el plugin que permite programar en python (pydev). Desde el men Help ir a Install

20

5. Entorno de desarrollo con eclipse (aptana)


Aptana Features y elegir la opcin de Aptana Pydev dentro de la zona de Web Applications Platforms.

21

5. Entorno de desarrollo con eclipse (aptana)

22

5. Entorno de desarrollo con eclipse (aptana)

Despus de la instalacin del plugin debemos proceder a la configuracin del entorno de Python que se usar para debugear las aplicaciones. Comenzaremos con la
creacin de un proyecto PyDev. Elegir el tipo de proyecto Python y luego la versin
del interprete. Antes de avanzar ser necesario configurar el interprete indicando dnde se encuentra ubicado python.

Luego despues de definir que el proyecto es de pydev, nos mostrara la ventana para
crear el proyecto y su workspace (espacio de trabajo), es importante definir el espacio
de trabajo en el directorio del openerp que fue instalado en el capitulo 1 que fue
/opt/openerp y dejar el interpretador de python el la versin 2.6.

23

5. Entorno de desarrollo con eclipse (aptana)

Seleccionamos auto config y apply luego OK.


Tendremos que instalar tambin el ambiente para el desarrollo con xml.
Volvemos al menu help-install new software luego seleccionamos el repositorio eclipse galileo y buscamos en el arbol web, xml, java desplegamos las opciones y seleccionamos eclipse xml editor tools.

24

5. Entorno de desarrollo con eclipse (aptana)

Casi listo ahora tenemos que configurar el entorno de ejecucin o sea como va ejecutar el openerp. Vamos al menu run run configurations.

Seleccionamos python run y agregamos una configuracin de ejecucin clicando en


el botn nuevo.
Tenemos tambien que renombrar el ejecutable de openerp que esta en
/opt/openerp/v6.1/openerp-br/server/openerp-server para /opt/openerp/v6.1/openerpbr/server/openerp-server.py con el comando en el terminal.
$ mv /opt/openerp/v6.1/openerp-br/server/openerp-server /opt/openerp/v6.1/openerp-br/server/openerp-server.py

Cerramos la ventana de ejecucin para actualizar la lista de archivos como cambiamos


un archivo a fuera del ide de eclipse, tenemos que refrescar el pydev package explorer
nos posicionamos en la lista de carpetas del proyecto en openerp le damos clic derecho y seleccionamos refresh (f5).

25

5. Entorno de desarrollo con eclipse (aptana)


Volvemos a entrar en el menu run run configuratin y volvemos al tem que creamos
arriba.

En main module clicamos en browse y seleccionamos el archivo renombrando dentra de la carpeta server que lo hicimos anteriormente.
Por ultimo y no menos importante como OpenERP se compone de mucho cdigo
repetitivo sobre todo a la creacin de las vistas. Por esta razn se crearon para el eclipse una serie de macros o snippets que facilitan introducir de estas partes de cdigos.
Vamos a prepara las pantillas para el uso con aptana. Hay dos archivos XML una
para ayuda en el desarrollo de python http://openerp-eclipse-template.googlecode.com/svn/trunk/templates-openerp.xml y otro para el desarrollo de XML http://openerp-eclipse-template.googlecode.com/svn/trunk/Openerp-eclipse-xml-template.xml
La primera diferencia es la ubicacin del editor de XML. En Eclipse se encuentra ubicados en distintos directorios por esa razn es necesario indicar correctamente el atributo content de los ficheros de XML.

Cambiamos la tag context especificando el lugar de los archivos xml por


context=com.aptana.ide.editors.contextType.text/xml

26

5. Entorno de desarrollo con eclipse (aptana)


Nos vamos al menu windows preferencias y seleccionamos en el rbol pydev editor templates y agregamos el xml del python llamado templates-openerp.xml

En seguida vamos a agregar el archivo para completar los xml. Usando la misma ventana pero en la configuracion de XML. Entrando em xml xml files editor templates.

27

6. Instalacin del pgadmin 3

6 Instalacin del pgadmin 3


pgAdmin es una herramienta para administrar las bases de datos de PostgreSQL.
Puesto que OpenERP trabaja con dicha base de datos, a veces puede resultar til tener
una herramienta grfica para poder administrar la base de datos.
$ sudo apt-get install pgadmin3

Con esto sera agregado en el menu principal en herramientas el pgadmin3 o en algunas distribuciones en el menu aplicaciones y luego en desarrollo.

Una vez arrancado, lo primero que debe hacerse es conectarse a una base de datos.
Para ello, se hace clic sobre el icono del enchufe y se abrir una nueva ventana para
introducir los datos del servidor PosgreSQL . En el campo Nombre debe introducirse
el nombre que se quiera dar a la conexin. En el servidor debe introducirse la direccin IP del servidor, o el nombre del Host (si se dispone de un servidor DNS que facilita la direccin IP), en este caso, puesto que el servidor y pgAdmin estn instalados
sobre la misma mquina, la direccin IP es localhost.

28

6. Instalacin del pgadmin 3

Una vez introducidos los datos, aparecer una pantalla advirtiendo del riesgo de almacenar la contrasea

Una vez efectuada la conexin con el servidor PosgreSQL, el programa muestra


nuevamente la ventana principal de pgAdmin III. Como puede comprobarse en la figura 4, aparece el nuevo servidor organizado en una estructura jerrquica.

29

6. Instalacin del pgadmin 3

Para acceder a las tablas, debe efectuarse doble clic sobre el servidor, en este caso
Empresa/Bases de Datos/empresa/Esquemas/public/Tablas y aparecern todas las tablas que contiene la base de datos a la que se ha conectado pgAdmin III.

Haciendo clic con el botn derecho del men sobre Tablas, se abrir un men desplegable que permite, entre otras cosas, crear una Nueva tabla.

30

6. Instalacin del pgadmin 3

Si se efecta el clic derecho sobre una tabla concreta, el men desplegable muestra
unas opciones distintas, entre la que cabe destacar la de Propiedades.

Si se hace clic sobre dicho men, se abrir una nueva ventana que ofrece informacin muy til relativa a la tabla en cuestin. En el presente ejemplo , la tabla que se
est consultando es res_partner_address de OpenERP, que contiene las direcciones de
las empresas. La pestaa Columnas ofrece una lista de los campos que componen dicha tabla.

31

6. Instalacin del pgadmin 3

La pestaa Restricciones ofrece la informacin relativa a las restricciones que el


mdulo OpenERP estableci en el momento de crear dicha tabla.

32

6. Instalacin del pgadmin 3


Si lo que se quiere ver son los datos contenidos en la tabla, hay que hacer clic sobre
el icono de la tabla que aparece en la parte superior, justo debajo del men principal.

Y aparecer una nueva ventana que mostrar los datos contenidos en la tabla en
cuestin. En la figura abajo se muestran los datos de la tabla res_partner_address

33

7. Openobject como RAD

7 Openobject como RAD


El openobject es el framework creado por openerp S.A para el desarrollo acelerado
de los mdulos de openerp. El framework utiliza los conceptos de mvc (modelo, Vista, controle).
OpenERP es un software cliente/servidor basado em python para planificacin de
recursos empresariales (ERP) Consta de un cliente OpenERP-Client y un Servidor
(OpenERP-server) mientras que la persistencia de datos esta proporcionada por la
base de datos relacional Postgresql

OpenERP utiliza el protocolo XML-RPC o NET-RPC para la comunicacin entre


cliente y servidor y tambin tenemos un cliente Web.
Una vez instalado, OpenERP tiene una estructura modular permitiendo aadir mdulos segn vaya siendo necesario.
OpenERP funciona sobre un entorno de desarrollo o framework llamado OpenObject, este framework permite el desarrollo rpido de aplicaciones (RAD Rapid Aplication development)
OpenObject es un entorno de desarrollo rpido de aplicaciones de gestin orientado a objetos en python usando la base de datos postgresql sus componentes son:

ORM (Object Relational Mapping) Mapeo de base de datos relaciona a objetos


Python.

Arquitetura MVC (Modelo (classe python), Vista (XML tipo html) y Controlador (controlador basado directamente en la base de datos)

34

7. Openobject como RAD

Sistema de flujos de trabajo llamado Workflows.

Diseo de informes o reportes (Utilizando OpenOffice (libreOffice), JasperReports o RML)

En desarrollo un herramienta de BI y OLAP cube Engine

Cliente GTK, KOO (KDE) y web que funcionan con el mismo codigode desarrollo de mdulos que estn en el servidor.

Todos los datos de Openerp son accesibles a travs de objetos. Por exemplo,
existe un objeto res.partner para acceder la informacin concerniente a parceros
(Partners)
Para un desarrollador que venga de la programacin orientada a objetos cabe sealar que en OpenERP el concepto objeto tiene un significado distinto a la de programacin orientada a objetos:
OpenERP

Programacin Orientada a Objetos


(Python)

Objeto

Clase

Recurso

Objeto o instancia de la clase

Obsrvese que existe un objeto para cada tipo de recurso, y no un objeto por recurso. As tenemos un nico objeto res.partner para manejar todas las empresas y no un
res.partner para cada empresa. Este objeto res.partner de OpenERP se programa como
una clase del lenguaje Python.

35

7. Openobject como RAD


7.1

Modelo, Vista, Controlador (MVC)

OpenERP utiliza el patrn de arquitectura de software Modelo Vista Controlador

Una definicin de este patrn podra ser la que ofrece Wikipedia: Modelo Vista
Controlador (MVC) es un patrn de arquitectura de software que separa en tres componentes distintos:
Los

datos de la aplicacin (modelo)

La

interfaz de usuario (vista)

La

lgica de control (controlador)

El patrn MVC se ve frecuentemente en aplicaciones web donde:


La

vista es la pgina HTML y el cdigo que provee de datos dinmicos a la

pgina.
El

modelo es el Sistema de Gestin de Base de Datos y la lgica de negocio.

El

controlador es el responsable de recibir los eventos de entrada desde la vis-

ta, consultar datos del modelos, realizar los clculos necesarios y solicitar nuevas vistas.
En aplicaciones complejas que presentan multitud de datos para el usuario, a menudo es deseable separar los datos (modelo) y la interfaz de usuario (vista), de manera
que los cambios en la interfaz de usuario no afectan a la manipulacin de datos, y que
los datos pueden ser reorganizados sin cambiar la interfaz de usuario. El modelo-vistacontrolador resuelve este problema mediante la disociacin del acceso a datos y la lgica de negocio de la presentacin de los datos y la interaccin del usuario, mediante
la introduccin de un componente intermedio: el controlador.
Por ejemplo, en el diagrama anterior, las flechas continuas que van desde el controlador a la vista y al modelo significan que el controlador tiene acceso completo tanto a

36

7. Openobject como RAD


la vista como al modelo. Las flechas discontinuas que van desde la vista hacia el controlador significan que la vista tiene un acceso limitado al controlador.
Las razones de este diseo de accesos limitados son las siguientes:

Acceso de modelo a vista: El modelo enva la notificacin a la vista cuando


sus datos han sido modificados, con el fin de que la vista pueda actualizar su
contenido. El modelo no necesita conocer el funcionamiento interno de la vista
para realizar esta operacin. Sin embargo, la vista necesita acceso a la partes
internas del controlador.

Acceso de vista a controlador: Las dependencias que la vista tiene sobre el


controlador deben ser mnimas ya que el controlador pueda ser sustituido en
cualquier momento.

En OpenERP, podemos aplicar este modelo-vista-controlador de la siguiente manera:

Modelo (model): Los objetos de OpenERP con sus columnas que normalmente se guardan en las tablas de PostgresSQL con sus campos. Permite la creacin/actualizacin automtica de las tablas y acceder a las tablas sin usar SQL.

Vista (view): Listas, formularios, calendarios, grficos, ... definidas en archivos XML. En estos archivos tambin se definen mens, acciones, informes,
asistentes, ...

Controlador (controller): Mtodos Python definidos dentro de los objetos de


OpenERP que proporcionan la lgica: Validacin de datos, clculos, ...

37

7. Openobject como RAD


7.2

Composicin de un modulo

La grande mayora de los mdulos de openerp contiene los siguientes elementos:

Business objects (objetos de negocios): Son declaraciones de clases de python que son heredadas de la clase osv.osv de openobject, la persistencia de
esta clase es manejada completamente por openobject.

Data (Datos): XML/CSV archivos metadatos (vistas e declaraciones de workflows), datos de configuraciones (parametrizacin de mdulos) y datos de demos (opcionales pero recomendable para pruebas)

Wizards: Formularios interativos usados para asistir a los usuarios a menudo


disponibles en las acciones contextuales sobre los recursos.

Reports: archivos RML (formato xml) o Openoffice reportes que genera reportes html, odt o pdf.

38

7. Openobject como RAD


7.3

Estrutura de carpetas de un modulo.

En la gran mayora de las veces los mdulos estn a dentro de carpeta en el servidor junto con los archivos de openerp-server, en la carpeta server/bin/addons. Pero es
posible tener modulos en otras carpetas desde que sean configuradas en el openerpserver.conf en la opcin addons_path que se puede agregar varios caminos diferentes
separados por coma (,).

addons/
| - ideia/
| - demo/
| - i18n/
| - report/
| - security/
| - view/
| - wizard/
| - workflow/
| - __init__.py
| - __terp__.py
| - idea.py

# Carpeta de los modulos


# Carpeta de un modulo especifico (ideia)
# Unittest, datos demos
# Archivos de traduccin
# Definiciones de reporte
# Declaracin de grupos y derechos de acceso
# Vistas (formularios, listas, menus y acciones)
# Definicin de los wizards
# Definicin de los workflow
# Archivo de inicializacin del propio python (requirido)
# Declaracin del modulo (requerido)
# Clases y modulo de objeto de python

Los nuevos mdulos pueden programarse fcilmente y requieren un poco de prctica en XML y Python.
Todos los mdulos estn ubicados en la carpeta bin/addons del directorio de instalacin del servidor OpenERP.

39

7. Openobject como RAD


Los pasos bsicos para generar un mdulo son:
Crear

un subdirectorio dentro de la carpeta bin/addons del servidor Ope-

nERP.
Crear

un archivo de inicio del mdulo de Python: __init__.py (que importa

los otros archivos .py que tenga el mdulo).


Crear

un archivo con la descripcin del mdulo de OpenERP:

__openerp__.py (Nota: Hasta la versin 5.0 se denominaba __terp__.py).


Crear

los archivos Python que contendrn los objetos (clases con atributos y

mtodos). Aqu es donde se define el modelo y el controlador.


Crear

los archivos XML para la definicin de datos (vistas, acciones, mens,

datos de ejemplo, ...). Estos archivos deben ser indicados dentro del archivo
__openerp__.py.
Opcionalmente,

crear informes/listados, asistentes y flujos de trabajo. Pueden

estar en subcarpetas (report, wizard, workflow,...)


Opcionalmente,

aunque recomendable, poner las traducciones en una carpeta

llamada i18n y las reglas de seguridad en una carpeta llamada security dentro
del mismo mdulo.
En todos los ficheros acostumbra a aparecer una cabecera que es un comentario Python referido a la licencia del mdulo y el copyright del autor/es, esto se puede copiar
de cualquier fichero de cualquier mdulo y adaptarlo a nuestras necesidades.
7.3.1 __init__.py
El archivo __init__.py (con doble guion bajo de cada lado) es un descriptor de um
modulo. Un modulo openerp es un modulo normal de python.
Ejemplo __init__.py
# Importar todos los archivos que contengan codigo python
Import idea, wizard, report

7.3.2 __terp__.py o __openerp__.py


Aqu es el segundo archivo que es obligatorio su existencia y es utilizado para definir la descripcin y dependencia del modulo, o sea este archivo es un descriptor que
contiene un diccionario de python con la declaracin de su nombre, dependencia, descripcin y composicin y los archivos utilizados en el modulo.
Exemplo: __openerp__.py
40

7. Openobject como RAD


{

'name': 'ideias',
'version': '0,1',
'category': 'tools',
'complexity': "easy",
'description': """modulo ideia para treinamento do modulo tecnical""",
'author': 'Fadel Damen Schreiner',
'website': 'www.qualquercoisa.com.br',
'depends': ['base_tools'],
'init_xml': [],
'update_xml': ['ideia_view.xml',
],
'demo_xml': [],
'test':[],
'installable': True,
'certificate': '',
'images': [],

Veamos los principales objetos:


'depends': ['base_tools'] : En openerp se puede tener dependencia entre modulos, o
sea para que funcione el modulo X es necesario el modulo Y. Pero tenemos el modulo
base_tools que depende del modulo base, notemos que solo nos referimos a
base_tools ya que openerp va instalar si o si el modulo base, que en realidad cualquier
modulo tiene que depender de este modulo.
El modulo base contiene: los mdulos mas bsicos de openerp que contiene las
definiciones de ir.property, res.compay, res.request, res.currency, res.users, res.partner.
En nuestro exemplo estamos utilizando el modulo idea, que en su definicion de dependencia de modulos tenemos base_tools, que es un modulo que contiene simplemente el menu tools.
'init_xml': [] : archivos xml con definiciones y/o datos que sean cargados o inicializados, al instalar solamente por primera vez un modulo o cuando se pasa en la linea
de comando al levantar el openerp con parmetro --init=<modulo>, normalmente no
es utilizado ya que tenemos otro parmetro que se utiliza para la actualizacin del
modulo que es el que esta mas abajo. Init tambien es utilizado cuando vamos levantar
una base de datos grande en alguna tabla por exemplo codigos postales, si pongo estos
datos cuando se hace un update de un modulo todas las vezes que se pueba una modificacin de codigo el va volver a instalar estos datos tardando siempre bastante tiempo.
'update_xml': ['ideia_view.xml'] : son los archivos de inicializacin del modulo que
son levantados cuando pasados como parmetro en la linea de comando del servidor
el --update=<moduloname> el metodo update siempre es ejecutado al instalar un modulo por eso no se utiliza mucho el parmetro anterior init_xml.

41

7. Openobject como RAD


'demo_xml': [] : son definiciones de dados demo del modulo, al instalar un modulo
el usuario puede seleccionar que se instale dados demostrativos dentro del modulo
para su mejor comprencin. Este es un grande ventaja entre los otros erp que nos trae
datos pre cardados para mejor compresin del modulo.
'test':[], : Otro diferencial de openerp en muchas oportunidade seguimos ciertos tratamientos en algunos datos con condiciones especiales que en algn momento del desarrollo sempre estropeamos esto generalmente pasa por estamos preocupado por las
nuevas alteraciones y al final descomponemos otras cosas en el sistema. Los testes
unitarios son para dejar en el instalador del modulo probar le sistema con estos datos
especficos y al probar el modulo el siempre trata estas condiciones y nos avisa si algo
esta mal.

Ejercicios 1
Crear la carpeta de un nuevo modulo llamado openacademy. Creando los archivos
__init__.py y __openerp__.py. El modulo propuesto es para control de entrenamiento trabajando con 3 tablas, Cursos (course), seccines (session) y participantes(attendee).

42

7. Openobject como RAD


7.4

Menus y acciones (actions)

7.4.1 Menu
la entidad menuitem es un atajo para la declaracin de un registro ir.ui.menu y conectarlo con la correspondiente accin a travs de un registro ir.model.data.
<menuitem id="menu_id" parent="parent_menu_id"
name="label" action="action_id" icon="icon-code"
groups="groupname1,groupname2" seguence="10"/>

id

Identificador del menuitem, debe ser unico

parent

Id del menu pariente, es la orden de su hierarqua

name

Opcional menu label (default: nombre de la accin.

action

Identifica la accin a ejecutar

icon

Icone para usar en el menu eg: terp-graph, STOCK_OPEN etc.

groups

Lista de grupos que van poder ver el menu (si vaco o no especificado todos
los grupos lo podran ver)

sequence

Indice entero para ordenar los menus.

Ejercicios 2
Crear el archivo openacademy_view.xml y crear la referencia al men para nuestro modulo.
Nos referimos a un men principal que no este adentro de ningn otro menu del sistema.

7.4.2 Declaracin de una accin


Acciones son declaraciones de registros regulares que son disparados con 3 metodos:

por clic en el men tem linkado con la accin especificada.

Por clic en el boton en una vista, que esta conectado a una accin.

Pon una contextual accin en un objeto.

accin
<record model="ir.actions.act_window" id="action_id">
<field name="name">action.name</field>
<field name="view_id" ref="view_id"/>
<field name="domain">[lista de 3 registros (maximo 250 caracteres)]</field>
<field name="context">{diccionario de context (max 250 caracteres)}</field>
<field name="res_model">object.mode.name</field>
<field name="view_type">form|tree</field>
<field name="view_mode">tree,form,calendar,graph</field>
<field name="target">new</field>
<field name="search_view_id" ref="search_view_id"/>
</record>

43

7. Openobject como RAD


Id

Identificador de la accion en la tabla ir.actions.act_window debe ser unico

Name

Nombre de la accin (requerido)

view_id

Especifica la vista a ser abierta (si no especificada, la vista con mayor prioridad es usado.

Domain

Campos para filtrar el contenido de la vista

Context

Diccionario de contexto a ser pasado a la vista

res_model

Es el nombre del objeto sobre el que opera la accin.

view_type

Se debe establecer a form para que la accin abra una vista de tipo formulario, y a tree cuando la accin deba abrir una vista de tipo rbol.

view_mode

Slo se tiene en cuenta si view_type se ha establecido a form; en cualquier otro caso, se ignora. Lista de vistas a usar (tree, form, calendar, graph, ...). Las cuatro posibilidades ms habituales
son:

form,tree: La vista se muestra primero en modo formulario, pero se puede mostrar


en modo lista clicando el botn Lista.

tree,form: La vista se muestra primero en modo lista, pero se puede mostrar en


modo formulario clicando el botn Formulario o haciendo doble clic sobre un elemento de la lista.

form: La vista se muestra como un formulario y no hay forma de cambiar al modo lis ta.

tree: La vista se muestra como una lista y no hay forma de cambiar al modo formula rio.

Target

Dejar para new para abrir la vista en una nueva ventana.

search_view_id

Identificador para que una nueva vista sea llamada en substitucin a la vista default.

44

7. Openobject como RAD


7.5

Servicio de objetos ORM

Un componente clave de openobject, es el servicio de objetos (OSV) que implementa un completo modelo relacional, liberando en la gran mayora de las veces que
se tenga que utilizar consultar sql. Incluso las DDL o alteraciones en la estructura de
las tablas.
Los Business objects (objetos de negocios), son declarados en archivos python
utilizando clases python que deben heredar de la clase osv.osv
archivo idea.py:
class idea_idea(osv.osv):
""" Idea """
_name = 'idea.idea'
_rec_name = 'name'
_columns = {
'user_id': fields.many2one('res.users', 'Creator', required=True, readonly=True),
'name': fields.char('Idea Summary', size=64, required=True, readonly=True, oldname='title',
states={'draft':[('readonly',False)]}),
'description': fields.text('Description', help='Content of the idea', readonly=True,
states={'draft':[('readonly',False)]}),
'comment_ids': fields.one2many('idea.comment', 'idea_id', 'Comments'),
'created_date': fields.datetime('Creation date', readonly=True),
'open_date': fields.datetime('Open date', readonly=True,
help="Date when an idea opened"),
'vote_ids': fields.one2many('idea.vote', 'idea_id', 'Vote'),
'my_vote': fields.function(_vote_read, fnct_inv = _vote_save, string="My Vote",
type="selection", selection=VoteValues),
'vote_avg': fields.function(_vote_avg_compute, string="Average Score", type="float"),
'count_votes': fields.function(_vote_count, string="Count of votes", type="integer"),
'count_comments': fields.function(_comment_count, string="Count of comments", type="integer"),
'category_id': fields.many2one('idea.category', 'Category', required=True, readonly=True,
states={'draft':[('readonly',False)]}),
'state': fields.selection([('draft', 'New'), ('open', 'Opened'),
('close', 'Accepted'), ('cancel', 'Refused')], 'State', readonly=True,
help='When the Idea is created the state is
\'Draft\'.\n It is \opened by the user, the state is \'Opened\'.\\n
If the idea is accepted, the state is \'Accepted\'.'),
'visibility':fields.boolean('Open Idea?', required=False),
'stat_vote_ids': fields.one2many('idea.vote.stat', 'idea_id', 'Statistics', readonly=True),
'vote_limit': fields.integer('Maximum Vote per User',
help="Set to one if you require only one Vote per user"),
}
_defaults = {
'user_id': lambda self,cr,uid,context: uid,
'my_vote': lambda *a: '-1',
'state': lambda *a: 'draft',
'vote_limit': lambda * a: 1,
'created_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'visibility': lambda *a: True,
}
_order = 'id desc'
def _check_name(self,cr,uid,ids):
for idea in self.browse(cr, uid, ids):
if 'spam' in idea.name: return False #No permitir spam
return True
_sql_constraints = [('name_uniq', 'unique(name)', 'Idea debe ser unica')]
_constraints = [_check_name, 'Por favor no haga spam', ['name'])]
idea_idea()

Atributos predefinidos osv.osv para los objetos de negocios


_name (requerido)

Nombre de objeto de negocios en dot-notation

_columns (requerido)

Diccionario de nombres de campos declaraciones de objetos y sus relaciones

_defaults

Diccionario {field names funcin que define el default


_defaults ['name'] = lambda self, cr, uid, context: 'eggs'

_auto

Si True (default) el ORM crea la tabla en la base de datos si esta False para crear la tabla /vista tie ne que utilizar el metodo init()

_inherit

_name nombre de una clase para trabajar heredar los atributos

_inherits

Herencia multiple

_constraints

Validaciones a travs de codigo python


(func_name, message, fields)

45

7. Openobject como RAD


_sql_constraints

Validaciones por la base de datos


(name, sql_def , message)

_log_access

Si True (default) 4 campos creados, (create_uid, create_date, write_uid, write_date) puede ser acecible por la funcin osv's perm_read()

_order

Nombre del campo para ordenar los registros en la lista (default: id)

_rec_name

Campo alternativo para uso en los nombres (_name) usado por el osv's name_get() (default _name)

_sql

Codigo SQL para crear tabla/vista para este objeto (si _auto es false) puede ser substituydo por el
metodo init()

_table

Nombre de la tabla a ser creado en la base de datos (default _name) con puntos '.' substituyendo el
underscore '_'

7.5.1 Tipos de campos del ORM


Los objetos de orm posee 3 tipos de campos: simples, relacionales y de funcin,
Los simples son: integers, floats, boolean, string etc. Los relacionales son campos que
posen relacin con otras tablas que puede ter: one2may, many2one, many2many. Y
por ultimo pero no menos importante los campos de funcin no son guardados en la
base de datos pero son calculados en el momento que se utilizan a travs de una funcin python.
Tipos de campos del ORM
Los atributos comunes son soportados por todos los campos

string Etiqueta del campo (requerido)

context: diccionario con los parametros contextuales


(para campos relacionales)
requerid: True si es obligatorio

change_default: True si el campo puede ser usado


readonly: True para que no sea editable
como condicin standart por valores en el cliente.
help: ayuda del campo
States: cambia dinamicamente el campo basado el los
select: 1 para incluir en la busqueda en las vistas y
otimizado para las listas e filtros (crea indice en la base de da- atributos del campo state
tos)
Campos Simples
'active' : fields.boolean('active'),
'prioridad' : fields.integer('Prioridad'), 'fecha_inicio' : fields.date('Fecha
Inicio'),

Boolean(...) integer(...) date(...)


datetime(...) time(...)

char(string,size,translate=False,...)

text(string,translate=False,...)
usuario.
Campos del tipo texto el text es mas utilizado para campos me-
mos
float(string, digits=None, )
Punto fluctuante con valores con precisin de decimales y/o
scala.

selection(values, string, )
Campo con valores pre-selecionados

binary(string, filters=None,...)
Contenido binario o archivos

translate: si True el campo puede ser traducido por el


size: tamao maximo para campos char

digits: si los digitos no son explicitos en un campo float


no de tipo decimal.
Values: lista de valores (key_label) o una funcin retornando campos
Filters: filtros opicionales para archivos
'fotos' :fields.binary('Fotos',filters='*.png,*.gif')

reference(string, slection, size, )


Campo con relacin dinamica con otros campos utilizando widget

46

7. Openobject como RAD


Tipos de campos del ORM
Campos Relacionales
Atributos comunes soportados por los campos relacionales

many2one(obj,ondelete='set null',....)
Relacionamento muitos para uno usando la clave primaria

one2many(obj, field_id,...)
Relacionamento virtual con multiplos objetos (inverso al many2one)

Obj:_name nombre del objeto destino (requerido)


field_id nombre del campo llave estranjera

many2may(obj, rel, field1,field2,...)


Relacin muchos contra muchos

Obj:_name nombre del objeto destino (requerido)


rel: tabla de relacionamiento (requerido)
field1 nombre del campo del rel.

Domain: opicional restriccion en los formularios sobre


argumentos de busquedas
Obj:_name nombre del objeto destino (requerido)
ondelete: al deletar plicar regla 'set null' o 'cascade' ver
documentacin del PostgreSQL

Ejercicios 3: Creando una class del objeto course


Definir una nueva clase para la definicin de la class openacademy_course con los campos
name y descripcin.

Campos de Funciones
function(fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, method=False,
store=False,multi=False,...)
Campo de funcion simula un campo real y es calculado por una funcion de python al vuelo
fnct: funcion que calcula el campo (requerido)
def fnct(self,cr,uid,ids,field_name,arg, context)
retorna un diccionario
fnct_inv: funcion usada para grabar el valor en el campo al vuelo
def fnct_inv(obj,cr,uid,id,name,value,fnct_inv_arg,context)
type: tipo del campo a ser simulado
fnct_search: funcion usada para buscqueda en el campo.
def fnct_search(obj,cr,uid,obj,name,args)
obj: modelo _name para simular que es un campo relacionales
store,multi usado como mecanismo de performace

related(f1,f2....,type='float',...) Shortcut fueld equivalent to browsing chained fields

f1,f2... chained fields to reach target (f1 requerid)

type: type of target field

property(obj,type='float', view_load=None, group_name=None,...)


Dinamic atribute with specific access rights

obj: objeto (requerido)

type: tipo equivalente del campo.

47

7. Openobject como RAD


7.5.2 Campos especiales y nombres reservados.
Algunos campos tienen nombres reservados por conveniencia por el framework algunos de estos son creados automticamente por el framework cualquier campo utilizado con estos nombre son ignorados por el mismo.
id

Sistema nico para identificar un objeto (creado por el orm)

name

Define el valor usado por default para mostrar los registros en una consulta o lista. Si ausente es necesario setar el
atributo _rec_name para especificar otro campo para este fin. Basicamente son los campos descripcin en las consultas por una clave primeria.

active

Define la visibilidad registros marcados con el campo active en Falso son escondidos por default, si no me equivoco
es utilizado al excluir un registro.

sequence

Define el orden y la habilidad de arrastras y soltar para reordenar si habilitado en las vistas.

state

Define el ciclo de vida el estagio del objeto usado en el workflow

parent_id

Utilizado para estructura en arbol de los registros

parent_left,
parent_right

Usado en conjunto con _parent_store permite un acceso rpido en la estructuras en rbol

create_date,
create_uid,
write_date,
wirte_uid

Usado para crear un log de dados del creador del registro, su ultima actualizacin. Estos campos son deshabilitados
si la pocion _log_access esta setada com False
obs: creado por el orm.

7.5.3 Metodos del ORM con los objetos osv.osv


Las clases derivadas de osv.osv que son utilizadas por todos las clases del openerp
tienen algunos metodos disponibles en los objetos de negocio, algunas veces invocadas con el objeto self de dentro de la propia clase y tambin a travs de otras clases
que ya estn estanciadas. Abajo fue creado una funcin _score_calc que utiliza le metodo browse
archivo idea2.py:
class idea2(osv.osv):
_name = 'ideia.ideia'
_inherit = 'ideia.ideia'
def _score_calc (self, cr, uid, ids, field, arg, context=None):
res = {}
for idea in self.browse(cr, uid, ids, context=context):
sum_vote = sum([v.vote for v in idea.vote_ids])
avg_vote = sum_vote / len(idea.vote_ids)
res[idea.id] = avg_vote
return res
_columns = {
'score': fields.function(_score_calc, type='float', method=True
}
idea2()

Mtodos del ORM en el objeto osv.osv


OSV acceso genrico

self.pool.get('object_name') puede ser usado para obtener el modelo de una clase


en cualquier lugar.

Parmetros standart utilizados por


varios mtodos.

cr: conexin a base de datos. (cursor)


uid: id de usuarios del sistema que esta haciendo la operacin.
ids: lista de registros de ids o un simpes entero contiendo 1 id.
context: diccionario opcional con los parametros contextuales.

create(cr,uid,values,context=None)
Crear nuevo registro con los valores
especificados Retorna el id del nuevo registro.

values: diccionario con los campos y valores del registro.


idea_id = self.create(cr,uid,
{'name': 'spam recipe',
'descripcion' : 'teste',
'inventor_id' : 45,})

48

7. Openobject como RAD


search(cr,uid,args,offset=0,
limit=None, order=None,
context=None, count=False)

args: lista de registros con las especificaciones de busqueda.


offset: opcional numero de registro a desconsiderar.
limit: opcicional maximo numero de registros a retornar.
order: opcional columna para ordenar default self._order)
count si true retorna solamente el numero de registros que caben en el criterio de
busqueda no los ids.
operadores: =, !=, >, >=, <, <=, like, ilike, in, not in, child_of, parent_left, parent_right.
operadores relacionales: '&' (default), '|', '!'
& and, | or, ! not
ids=self.search(cr,uid, [ '|', ('partner_id', '!=', 34),'!', ('name', 'ilike', 'spam'),],order='partner_id')

read(cr,user,ids,fields=None,

fields: opcional lista de campos a retornar (default: todos los campos)


context=None)
retorna un diccionario com los valo- results = self.read(cr, uid, [42,43], ['name', 'inventor_id'])
res de los campos solicitados.
print 'inventor:', results[0]['inventor_id']
write (cr,uid,ids,values,

values: diccionario de los campos y sus valores a actualizar


context=none)
actualizar registros con los valores self.write(cr, uid, [42,43],{ 'name' : 'nombre parcero ', 'partner_id': 24,}])
pasados en los ids selecionados
copy(cr, uid, id, defaults,
context=None)
duplica registros con el id pasado
con los valores defaults
retorna true

defaults: diccionario de campos y valores a cambiar de la copia antes de salvar los


objetos.

unlink(cr, uid, ids, context=None)


Borra registros con los ids pasados
Retorna True

self.unlink(cr,uid,[42,43])

browse(cr,ids, context=None)
Busca los registros permite el uso
del dot-notation para listar los campos y las relaciones
Retorna: un objeto con la lista de
objetos requisitados.

ideia=self.browse(cr,uid,42)
print 'Ideia descripcin', ideia.description
print 'Inventor country code:', ideia.inventor_id.address[0].country_id.code
for vote in ideia.vote_ids
print 'vote %2 2f' % vote.vote

default_get(cr,uid,fields,
fields: lista de los nombre de campos
context=none)
retorna un diccionario con los valo- defs=self.default_get(cr, uid, ['name', 'active'])
res default de los campos (seteados assert defs['active']
en la clase de objetos o por la preferencia de objetos o via context
perm_read(cr,uid,ids,details=True)

Details: si True, sustituye los campos *_uid con el nombre de usuario.

Retorna un diccionario con dados Retorna un diccionario con los object id (id), Id do criador (create_uid), fecha de la creacin
de quien creo y cuando creo los re- (create_date), id usuario que actualizo (write_uid) y fecha de la actualizacin (write_date)
gistros.
perms= self.perm_read(cr,uid,[42,43])
print 'Criador:', perms[0].get('create_uid', 'n/a')
fields_get(cr,uid, fields=none,
fields: lista de campo.
context=none
retorna un diccionario de dicciona- class ideia(osv.osv):
rios de campos, cada un con la des(...)
cripcin de los campos del objeto
_columns = {
'name': fields.char('name',size=64)
(...)
def test_fields_get(self,uid):
#assert -> tira una execcin si no retorna verdadero)
assert(self.fields_get('name')['size']=64)
fields_view_get(cr,uid,
view id=None,view_type='form',
context=None,toolbar=False)

view_id: id da vista o vazio (none)


view_type: tipo de la vista a retornar si vazio trae todas
toolbar:True para incluir la accin contextual

devuelve un diccionario que describe la composicin de una vista soli- def test_fields_view_get(self,cr,uid):
citada (incluyendo las vistas hereideia_obj=self.pool.get('ideia.ideia')
dadas)
form_view=idea_obj.fields_view_get(cr,uid)
name_get(cr,uid,ids,context={})
name_search(cr,uid,name='',
args=None, operator='ilike',
context=None, limit=80)
export_data(cr, uid, ids, fields,
contex=None)
import_data(cr,uid,ids,fields,
context=None)

49

7. Openobject como RAD

7.5.4 Mecanismo de la herencia

50

7. Openobject como RAD


7.6

Construyendo la interface del usuario.

Para construir un modulo, el principal mecanismo es insertar registros en la declaracin de mdulos del componente de interface. La interface del usuario son registros en
la base de datos conteniendo los mens, vistas, acciones, accesos etc.
El mecanismo de OpenERP para agregar registros es usando archivos XML
con una estructura predefinida.

7.6.1 Estructura comun del XML


Xml declarados en el atributo update_xml de un mdulo contienen declaraciones
de registro en el siguiente formato:

<?xml version="1.0" encoding="utf-8"?>


<openerp>
<data>
<record model="object_model_name" id="object_xml_id">
<field name="field1">value1</field>
<field name="field2">value2</field>
</record>
<record model="object_model_name2" id="object_xml_id2">
<field name="field1" ref="module.object_xml_id" />
<field name="field2" eval="ref('module.object_xml_id')"/>
</record>
</data>
</openerp>

Para cada tipo de registro (view, menu, action) tiene soportados un conjunto de entidades y atributos, pero todos ellos comparten algunos atributos especiales:
id

Unico por modulo, identificador de los registros (xml_id)

ref

Utiliza en lugar del contenido del elemento para hacer referencia a otro registro (funciona entre mdulos anteponindole el
nombre del mdulo)

eval

Utiliza en el lugar del contenido y proveer dados usando expresiones python, en el ejemplo utiliza el mtodo ref() que es
utilizado para encontrar el id en la base de datos para encontrar el xml_id

Consejo: validacin del xml por relaxng


Openobject valida la estructura del xml, de acuerdo a las reglas de relaxng encontrado en server/bind/import_xml.mg.
Para una chequear use el comand:
xmlint xmlint relaxing /path/para/el/import_xml.mg <file>

51

7. Openobject como RAD


7.6.2 La sintaxis los archivos CSV
Los archivos CSV que son agregados por el update_xml y los registros inseridos por
el metodo del osv import_data() usando el nombre del archivo para definir el alvo de
modelo de objeto. El ORM automticamente conecta los relacionamientos en base a
los nombre de columnas abajo:
Id (xml_id)

Columna contiene el identificador del relacionamiento

many2one_field

Conecta los campos usando el name_search()

many2one_field:id

Conecta los campos basado en el objeto xml_id

many2one_field.id

Conecta los campos basado en el objeto database id

ir.model.access.csv
"id"
,"name"
,"model_id:id"
,"group_id:id"
,"perm_read","perm_write","perm_create","perm_unlink"
"access_idea_idea","idea.idea","model_idea_idea","base.group_user",1
,0
,0
,0
"access_idea_vote","idea.vote","model_idea_vote","base.group_user",1
,0
,0
,0

7.6.3 Vistas y Herencias de vista.


Las vistas son la forma como se presentara los datos en el formulario puede ser de
los tipos arbol (tree), formulario (form), busqueda (search), calendario (calendar),
grantt (grantt), graficos (graphs). Con las vistas es posible aplicar conceptos de herencia pudiendo agregar y remover caractersticas.
Forma genrica de declarar una vista
<record model="ir.ui.view" id="view_id">
<field name="name"> view.name </field>
<field name="model">object_name</field>
<field name="type">form</field> # tree,form,calendar,search,graph,gantt
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<!-- view content: <form>, <tree>, <graph>, ... -->
</field>
</record>

id

Unico identificador de la vista

name

Nombre de la vista

model

Modelo del objeto (mismo que res_model en la accin

type

Tipo de la vista, form,tree,graph,calendar,search,gantt

priority

Prioridad del a vista cuanto menor mayor la prioridad.

arch

Arquitetura de la vista, usado para tener varias vistas definidas

Forms formularios (para la vista y edicin de registros).


Los formularios permiten la creacin y ediccin de los recursos (edit labels etc)
para los usuarios y utilizan el elemento <form> en el xml.

52

7. Openobject como RAD


Un modelo de la declaracin de un formulario seria.
<form string="Idea form">
<group col="6" colspan="4">
<group colspan="5" col="6">
<field name="name" select="1" colspan="6"/>
<field name="inventor_id" select="1"/>
<field name="inventor_country_id" />
<field name="score" select="2"/>
</group>
<group colspan="1" col="2">
<field name="active"/>
<field name="invent_date"/>
</group>
</group>
<notebook colspan="4">
<page string="General">
<separator string="Description"/>
<field colspan="4" name="description" nolabel="1"/>
</page>
<page string="Votes">
<field colspan="4" name="vote_ids" nolabel="1" select="1">
<tree>
<field name="partner_id"/>
<field name="vote"/>
</tree>
</field>
</page>
<page string="Sponsors">
<field colspan="4" name="sponsor_ids" nolabel="1" select="1"/>
</page>
</notebook>
<field name="state"/>
<button name="do_confirm" string="Confirm" icon="gtk-ok" type="object"/>
</form>

El codigo arriba nos arrojara el formulario abajo.

Elementos de un formulario
string

Label (etiqueta para un elemento)

nolabel

Atribuir a 1 para Esconde el label de elemento

colspan

Numero de columnas que el objeto tomara

col

Numero de columnas que tendr internamente al objeto

rowspan

Numero de lineas que ocupara el elemento

invisible

Atribuir a 1 para que el objeto sea invisible

eval

Evalua con un codigo python el contenido de un edit por exemplo

attrs:

Mapeo en python para atribuye dinamicamente condiciones para los atributos readonly, invisible, requerid basado
en el contenido de otros campos.
Field

Atribuye widgets atomaticamente dependendo de los tipo de campos:


string: etiqueta para los campos y usado para la busqueda (sustituye el nombre del campo)
select: deja disponible el campo para las bsquedas ,y crea en la base de datos un indice al mismo se lect con el atributo 2 son para las bsquedas avanzadas.

53

7. Openobject como RAD


nolabel: atribuyendo a 1 no muestra el label.
requerid: sustituye los campos requeridos en la clase.
readonly: tambin sustituye en la definicin de las clases si va ser solamente lectura.
password: atribuyendo a true no muestra los caracteres digitados
context: declaracin de cdigo python a un diccionario de contexto.
domain: declaracin en python de las restricciones de registros restringidos por valores.
on_change: llama a un metodo python llamado por un trigger cuando el valor de l campo cambia
completion: en 1 para habilitar el auto-competation de los valores posibles .
groups: lista separados por coma de los grupos que estan habilitados a ver este campo.
widget: seleccionar un widget (one2may_list, many2many, url, email, image, float_time, reference,
text_wiki, text_html, progressbar)
propierties

Widget dinamicos mistrando todos las propriedades (no atributos)

Button

widget clicable (un boton) que se puede atribuir una accin y especificar los atributos:
type: tipo del botton, workflow (default), accion o objeto.
name: sinal del workflow, nombre de una funccion, o accin dependiendo del atributo type.
confirm: texto para el mensaje de confirmacin cuando clicado.
states: lista de estados que va cambiar el boton separado por coma.
icon: opcional icon (todos los iconos del GTK ej. gtk-ok)

Separator

Linea horizontal para separa la estructura de las vistas, con label opcional

Newline

Forza que sea final de linea para las vistas

Label

Texto libre como label o legenda de un formulario

Group

Usado para organizar los campos en forma de grupo con label opcional

Notebook,
page

Notebook elementalmetne es una tab, y posee los atributos


name: nombre para la pagina/tab
position: posicin de los tabs en el notebook( inside, top, bottom, left, right por adentro, arriba, abajo, a
la derecha o a la izquierda.

Una de las tareas mas complejas en las vistas son ordernar y organizar los campos
(atributos) en el formulario y para eso se utiliza los atributos colspan y col son reponsables de definir con colspan la cantidad de columnas que el objeto va tomar y col
para definir cuantas colunas a dentro del objeto sera creado. Esta forma de dibujar es
muy utilizado en el desarrollo web, y como ya es muy utilizada y ya se considera una
excelente forma de tratar objetos, fue utilizado tambin por los desarrolladores de
OpenERP.
Vamos a ejemplo:
<group col="6" colspan="4">
<group colspan="5" col="6">
<field name="name" select="1" colspan="6"/>
<field name="inventor_id" select="1"/>
<field name="inventor_country_id" />
<field name="score" select="2"/>
</group>
<group colspan="1" col="2">
<field name="active"/>
<field name="invent_date"/>
</group>

54

7. Openobject como RAD


En un formulario standart esta dividido en 4 columnas, as:
<form>

1 2 3 4

</form>

En estas cuatro columnas se distribuyen los label y los edits.


<form>
<field name="input" />
<field name="inpt2" />
</form>

Esto en las vistas serian presentados como abajo:

label input labl2 inpt2

En la primera columna se mantiene el label y en el siguiente es su widget de ediccin (edit)


Usando el colspan podremos definir que un campo tenga un tamao especifico por
exemplo:
<field name="inpt4" colspan="4"/>
<field name="input" />
<field name="inpt2" />

labl4 inpt4_______________

label input labl2 inpt2

Decimos que el campo inpt4 va ocupar 4 columnas y nos presenta el label en 1 columna y el edit en 3 columnas ya que un field contiene un label y su widget.
O tambien en diferentes ordenes.

<field name="input" />


<field name="inpt4" colspan="4"/>
<field name="inpt2" />

label input

labl4 inpt4_______________

labl2 inpt2

55

7. Openobject como RAD


tambin tenemos la capacidad e de definir que un elemento de tipo group ocupe 4
columnas y dividimos estas cuatro columnas que ocupara el group en 6 columnas hijas utilizando el elemento col. Veamos el ejemplo.
<group cols="2" colspan="2">
<field name="a" />
<field name="b" />
</group>
<group cols="6" colspan="2">
<field name="d" />
<field name="e" />
<field name="f" />
</group>

Esto se tranforma en:

lb a
lb d lb e lb f

lb b

Lo nico que tiene siempre que recordar es que los field tienen su label que ocupara siempre una columna.
Y lgicamente podremos complicar por ejemplo aadir un group dentro de outro
group.
Listas / Arboles (tree)
Otra forma de presentar los datos son a travs de las vistas tree que son posibles
presentar los dados como un grid, o como un arbol dependiendo de los tipo de datos
que tenemos. Su declaracin es:

<tree string="Idea Categories" toolbar="1" colors="blue:state==draft">


<field name="name"/>
<field name="state"/>
</tree>

Atributos

Colors: lista de mapeo de colores utilizando condiciones en python


editable: permite que el grid sea editable.
Toolbar: al setear a true muestra la hierarqua en una toolbar.

Elementos permitidos

Field,group, separator, tree, button, filter,newline

56

7. Openobject como RAD


Recuerden que es necesario declarar la vista de forma completa y una por cada tipo
que hasta ahora tenemos los Tree y Form.

Ejercicios 4: Creando los submenus , accin y las vistas form y tree.


Definir los menus hijos del menu principal creado anteriormente llamado openacademy.
Crearemos 2 entradas una lista de todos los cursos y la opcin de agregar y modificar nuevos cursos eso implica en crear las vistas form y tree como tambin crear la accin a los
menus.

Ejercicios 5: Class session


Crear la clase python para las secciones (seccin), la misma va tener los campos:
Name
char
128
requerido.
start_date
date
duration
float
Seats
integer
Crear su vistas (form e tree) como tambin el menu y actin.

Ejercicios 6: campos relacionales


Ya tenemos 2 tablas cursos y secciones pero todavia no tenemos ninguna relacin entre
las mismas, utilizando de los campos relacionales crearemos la relacin un curso puede
tener varias secciones. Creamos el campo 'course_id' en la clase session utilizando la relacion many2one.
Ademas vamos agregar tambin en la vista form de session este campo course_id

Ejercicios 7: Notebook
Vamos ordenar la vista formulario de los cursos utilizando el notebooks para separar la
descripcin de los cursos y tener oportunidad de agregar nuevas pestaa con otros datos.

Ejercicios 8: classe Attendee (participantes)


Crear la ultima clase attendee (participantes) que contendr 2 campos many2one que serian:
partner_id relacionado con la classe res.partner
session_id relacionado con la classe openacademy.session

57

7. Openobject como RAD


Ejercicios 9: Actualizando la clase y vista session
Como creamos mas una clase que esta relacionado con la clase session tenemos que
atualizar esta clase con el nuevo campo attendee y tambien la vista form de la clase session.

Ejercicios 10
En el ejercicio 6 habiamos creado en la clase seccin el campo relacional curse_id pero no
creamos la contrapartida en la clase course o sea agreguemos el campo 'session_ids' del
tipo one2many que nos va dar la posibilidade de aceder a los respectivos datos de qualquier una de las clases.
Otro cambio que podremos hacer al la clase cursos es tambien agregar un responsable al
curso que va usar el usuario del sistema como base para guardar quien agrego el curso y
va usar la clase ya existente de openerp res.user. El campo seria:
'responsible_id' del tipo many2one relacionado a la clase res.user.
Vamos agregar tambien estos campos en sus respectivas vistas siguiendo la imagen abajo.

Consejo: utilizar la tag group siempre que utiliza 2 tipos de vistas y/o notbook para que el
layout no tenga comportamientos inesperados.

Herencia en vistas
Las vistas existentes solamente deben ser modificadas a travs de vistas heredadas,
nunca directamente. En la vista heredada se hace referencia a la vista padre usando el
campo inherit_id, para luego adicionar modificar los elementos existentes en la vista
por referencia usando expresiones Xpath especificando la posicion.
Position

Inside: pone adentro de un elemento


replace: substituye un campo
before: despues del campo
after: antes del campo

<record id"idea_category_list2" model="ir.ui.view">


<field name="name">id.category.list2</field>
<field name="model">ir.ui.view</field>
<field name="inherit_id" ref="id_category_list"/>
<field name="arch" type="xml">
<xpath expr="/tree/field[@name='description']" position="after">
<field name="idea_ids" string="Numero de la idea"/>
</xpath>
</field>
</record>

El formato Xpath se utiliza mas cuando tenemos campos con el mismo nombre en la vista, otra forma
es utilizando directamente el nombre del campo

58

7. Openobject como RAD

<record id"idea_category_list2" model="ir.ui.view">


<field name="name">id.category.list2</field>
<field name="model">ir.ui.view</field>
<field name="inherit_id" ref="id_category_list"/>
<field name="arch" type="xml">
<field name==description position="after">
<field name="idea_ids" string="Numero de la idea"/>
</field>
</field>
</record>

Ejercicios 11: herencia de clase y vista


Como tenemos la clase attendee (participantes) creamos esta clase utilizando la clase partner para saber quienes son los alumnos pero tambien utilizamos partner para definir quien
es instructor el la clase seccion. En el futuro quiero que quando trate de seleccionar un ins tructor no me traiga en el sitado todos los partner y si solamento los que fueron cargados
como intructor, tendriamos 2 formas de hacer eso, creando una nueva clase instructor, o
creando un campo en partner que diga quando se esta cargando un instructor. Entonces
vamos utilizar la propria clase partner y agregar un campo llamado 'is_instructor' donde
sera un campo boleano si el partner creado es un instructor. Para hacerlo tenemos que utilizar herencias ya que si cambio el modulo original cuando se actualize el modulo stantart
perderiamos el cambio.En este Ejercicio utilizando el recurso de herencia tanto de la clase
como de la vista agreguemos el campo is_instructor tipo bolleano que va estar posicionado luego despues del campo Suppier de la vista partner.

59

7. Openobject como RAD

7.7

Dominios en campos de las clases.

Los dominios son las condiciones que modifican el resultado de una consulta. Las expresiones son indicadas con un array, que contiene las condiciones, y en ocasiones, el operador
booleano para unirlas. Veamos un ejemplo:

[ ('name', 'like', 'As'), '|', ('active', '=', True)]

Imaginemos que el anterior dominio lo aplicamos a un listado de clientes, el resultado ser


todos los clientes que contenga en su nombre As o que la empresa est activa.

Los operadores de las condiciones que podemos usar son:

= , igual
<> o != , distinto
<= , menor o igual que
< , menor que
> , mayor que
>= , mayor o igual que
=like , contiene
like , contiene
not like , no contiene
ilike , contiene (ignorando las maysculas y las minsculas)
not ilike , no contiene (ignorando las maysculas y las minsculas)
in , existe en un array
not in , no existe en un array
child_of , es hijo de

Los operadores booleanos permitidos son:


&,Y
|,O
! , NO

Ejercicios 12: Trabajando con domain en clases


Agregar un dominio a la busqueda del campo instructor_id que solamente traiga los registros que en el partner este en verdadero el campo is_instructor y supplier tambin sea verdadero.
Tambin agreguemos en la vista formulario de la clase session que se pueda ver los ins tructores relacionados.

60

7. Openobject como RAD


Un Observacin importante en el contexto de los dominios es que se pueden aplicar en la clase y la misma aplicara a todo el sistema donde sea usado, el campo o podremos utilizar el dominio solamente en una vista especifica hagamos este cambio
para ver que el funcionamiento es lo mismo pero solamente se aplicaria a la vista que
estamos trabajando.

Ejercicios 13: Aplicando el uso de dominio solamente a la vista.


Cambiemos el uso del dominio solamente el la vista del modulo.

Como en el momento de cargar un participante (attendee) que en realidade es nuestra clase de partnet seria interesante que al momento de cargar el partner ya hagamos
la carga de la seccion que el alumno va participar. Hagamos estos cambios.

Ejercicios 14: En la clase partner agregar las sessiones.


Agreguemos en la clase partner heredada la posibilidad de ya agregar la seccin del curso
en el momento de su carga.

61

7. Openobject como RAD


7.8

Depuracin de cdigo con Aptana

Como vamos a trabajar con codigos en python que sale um poco del habito del framework creando funciones, metodos etc tenemos que comprender como depurar el
cdigo fuente para mejor manejo de los errores.
Este mtodo es usado en aptana o con el eclipse ya que simplesmente es la forma
de trabajo de eclipse.
Lo primero que tenemos que tener en cuenta es que si o si tenemos que tener el servidor de openerp funcionando en la maquina de desarrollo ya que para depurar tenemos que correr el servidor por el aptana incluso con parametros que veremos abajo.
Como ya lo hicimos en el capitulo de instalacin de ide aptana gran parte de la
configuracin para que todo funcione. Pero como normalmente tenemos que pasar
como argumento al servidor openerp el update para que las modificaciones que hacemos en el codigo se aplique a la base de datos, tenemos que hacer que openerp corra
por el aptana y poner los parametros de conexin.
Tenemos que al correr openerp-server passar los parametros para que levante el servidor por el aptana y permita utilizar la depuracin. Con eso entramos en el menu runrun configurations y luego seleccionamos en el arbol el openerp-run que creamos al
principio del manual, e vamos a la haba parametros. Como mostrado abajo y agregamos los argumentos.

62

7. Openobject como RAD


Lo principal es agregar el siguiente parametros para correr el servidor:
-r openerp -w openerp --addons-path=/opt/openerp/v6.1/openerp-br/addons,/opt/openerp/v6.1/openerp-br/web/addons
update=openacademy --debug

Donde:
-r es el usuario del sistema que tiene permisiones de correr la base de datos
-w contrasea del usuario del parametro -r
--addons-path= lugar donde estn guardados los addons puede contener una
lista de carpetas separados por coma.
--update utilizado para decir al servidor que levante o actualize el codigo de un
modulo especifico o all para todos.
debug modo depuracin en los logs de openerp o en el console.
Antes de correr el servidor tenemos que poner los Breakpoints en el codigo para
que el sistema pare en determinado lugar en el codigo.
Para crear los breakpoints solamentes tenemos que dar doble click en el editor al
costado de la linea que queremos crear el breakpoint. Quedando con el aspecto abajo.

Luego podremos correr el sistema em modo depuracin utilizando el menu run


debug (f11) y nos va presentar la siguiente pantalla.

63

7. Openobject como RAD

Ai nos pregunta si queremos cambiar a la perspectiva de depuracin que nos muestra las variables, estados etc. Respondemos que s.
Y luego entramos al sistema y nos vamos a la pantalla que contiene el breakpoint que
en nuestro ejemplo esta en la clase session la y va quedar parado y veremos la pantalla
abajo:

64

7. Openobject como RAD


7.9

Utilizacin de campos tipo funcin y objetos de ORM

Empecemos a utilizar campos calculados a trelados a funciones y/o mtodos. En


OpenERP se utiliza los campos funcionales mostremos aqu otra vez su utilizacin:
fields.function(fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, method=False,
store=False,multi=False,...)
Campo de funcion simula un campo real y es calculado por una funcion de python al vuelo
fnct: funcion que calcula el campo (requerido)
def fnct(self,cr,uid,ids,field_name,arg, context)
retorna un diccionario
fnct_inv: funcion usada para grabar el valor en el campo al vuelo
def fnct_inv(obj,cr,uid,id,name,value,fnct_inv_arg,context)
type: tipo del campo a ser simulado
fnct_search: funcion usada para bsqueda en el campo.
def fnct_search(obj,cr,uid,obj,name,args)
obj: modelo _name para simular que es un campo relacionales
store,multi usado como mecanismo de performace

En este tipo de campo puede o no mantener los datos armazenados en la base de


datos, usando por exemplo el atributo store=True, y se puede utilizar tambin una funcin especifica que va gravar los datos en la base de datos, con el parametro fnct_inv.
Una funcin siempre en una clase en python debe siempre tener en la declaracin
de sus parametros el self.

Ejercicios 15: Campos calculados


Crear un campo calculado que muestre el porcentaje de cupos que tiene una seccin de
cursos, por ejemplo una seccin se define que tiene 10 cupos y que muestre el porcentaje
que tenemos completo el cupo.
Ejemplo:
Veamos que tenemos un barra de progreso en nuestra vista.

65

7. Openobject como RAD


7.10 Eventos en las vistas
En las vistas tenemos situaciones de control de evento, o sea un metodo llamado
on_change que son utilizados en las verificaciones en los campos al vuelo, o sea por
ejemplo tenemos el campo seats que son los asientos (cantidad de vagas que puede tener una seccin) este valor no puede ser negativo, y ademas todas las vezes que cambie el valor a este campo tendria que actualizar el campo calculado remaining seats
asientos restante automticamente esta opcin solamente podra ocurrir con un evento
o sea al salir de un campo especifico. En OpenERP utilizamos el atributo en la vista
llamado on_change con en nombre del metodo que va ejecutar (codigo python).
Ay una regla muy importante en el on_change que se debe retornar un diccionario
de atributos valores. Esto se maneja as para poder cambiar valores de campos en la
vista como tambin presentar mensajes a los usuarios.
Veamos un ejemplo de la definicin de un evento on_change en la vista:
<field name="shop_id" on_change="onchange_shop_id(shop_id)"/>

En la vista es bastante sencillo simplesmente utilizamos el atributo on_change al


campo pasando el nombre del metodo python con su parmetros por ejemplo el
shop_id.
Abajo el mtodo en python:
def onchange_shop_id(self, cr, uid, ids, shop_id):
v={}
if shop_id:
shop=self.pool.get('sale.shop').browse(cr,uid,shop_id)
v['project_id']=shop.project_id.id
if shop.pricelist_id.id:
v['pricelist_id']=shop.pricelist_id.id
v['payment_default_id']=shop.payment_default_id.id
return {'value':v}

En el codigo python siempre debe devolver un diccionario de datos ya que openerp


utiliza eso para separa y tomar acciones. Un diccionario tiene rotulo, en este caso podremos tener los siguientes rtulos:
'value':

{'field': 'valor','field2','valor2'}

'domain': {'field':'domain'}
'warning': {'title':'mensaje'}

66

7. Openobject como RAD


Cuando pasamos el diccionario con el el rotulo 'value' es posible cambiar los valores de otros campos en el form tipo un recalculo o atribuir otro valor a los mismos y
puede ser varios campos a la vez, lo mismo pasa con los dominios.
Tememos el rotulo 'warning' que puede ser pasado junto con cambios de campos
pasando un diccionario as

dict={'value':{campo1:valor,campo2:valor2},'warning':

{'title': 'mensaje'}}, quando openerp recibe un diccionario como el tipo 'warning' presenta una mensaje en la pantalla del usuario con el titulo y mensaje pasado por el diccionario.

Ejercicios 16: on_change

Crear un evento para el campo seats que valide si el valor de seats es negativo presenta una mensaje y vuelve los valores que tenia grabado en la base de datos.

67

7. Openobject como RAD


7.11 Contraints
Siempre que hablamos de base de datos este concepto hasta hoy en las mismas son
poco utilizadas, normalmente son aplicadas solamente en la camada aplicaccin y habre brecha de poder tener dados con consistentes en la base de datos. Sabemos que
existe casos donde es mejor aplicar las contraints en la base de datos, pero en la mayoria de las vezes quedan apenas en la camada aplicacin.
En openerp tenemos la posibilidad de hacer de las 2 formas tambin pero en la base
de datos es mucho mas fcil.
De las 2 formas son declaradas en la clase de openerp utilizando los siguientes atributos del ORM osv.osv. Los nombre de estos atributos son:
_constraints

Validaciones a travs de codigo python


(func_name, message, fields)

_sql_constraints

Validaciones por la base de datos


(name, sql_def , message)

Ejercicios 17: Constraints

Crear 2 constraints de los tipos _sql_constraints y _constraints, la primera validando


que el titulo de los cursos sean unicos (no permita que sean iguales) y la segunda
verificando si el titulo y descipcin son iguales, al dar guardar en la tabla de cursos.

68

7. Openobject como RAD


7.12 Herencia en metodos.
En OpenERP otro tipo de herencia que se puede utilizar el la herencia de metodos,
que nada mas es cambiar el comportamiento de un metodo existente.
Eso es una caracterstica de python y sigue sus mismas reglas.
En la herencia tenemos normalmente 2 formas de trabajar o substituyendo totalmente el mtodo o simplemente cambiando parte del mismo. Para reimplementar una
clase solamente la reescribimos por completo ya que en teoria no queremos solamente
cambiar alguna funcionamiento. Ahora tratndose de una clase que queremos cambiar
su parte de su funcionamiento utilizamos en python la funcin super().
En ejemplo podremos cambiar la funcionalidad de la clase antes o despues de su
ejecucin todo depende claramente de lo que queremos hacer.
def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
res=
super(account_invoice_line, self)._amount_line(cr, uid, ids, prop,
unknow_none,unknow_dict)
res = {}
for line in self.browse(cr, uid, ids):
res[line.id] = round(line.price_unit * line.quantity * ((line.taux)/100.0) *(1-(line.discount or
0.0)/100.0),2)
return res

El secreto es utilizar la funcin super() de python en el ejemplo se ejecutara el metodo anterior luego se tratara algunos valores. Tambin es possible tratar algun dato
antes de enviar al otro metodo.
def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
res = {}
for line in self.browse(cr, uid, ids):
res[line.id] = round(line.price_unit * line.quantity * ((line.taux)/100.0) *(1-(line.discount or
0.0)/100.0),2)
return
super(account_invoice_line, self)._amount_line(cr, uid, ids, prop,
unknow_none,unknow_dict)

Ejercicios 18: herencia de metodos

Como hicimos los contraints que verifican que no se pueda duplicar un nombre de
curso si tratarmos de utilizar el recurso de openerp de duplicar un registro nos va
generar un error acionando la constraints para eso tenemos que re-implementar el
metodo copy estandart para que por exemplo agregue al nombre nuevo por exemplo
el termino (copia).

69

7. Openobject como RAD


7.13 Atribuyendo valor default a campos
Unos de los mecanismos e un sistema que es muy utilizado para facilitar y mantener dados coerentes es atribuir a certos campos valores default y ademas podremos incluso no dejar que el usuario cambie este dato. Por ejemplo fecha de movimiento.
En OpenERP es posible utilizar el atributo de osv.osv que es de que deriva nuestras
clases creadas para los modulos y definir valores por defecto utilizamos el atributo
_defaults en el codigo phyton del modulo.
_defaults = {
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
}

En principio es bastante simples es declarar: 'campo' : valor, 'campo2' : valor2 en el


ejemplo arriba estamos utilizando un recurso de python que es el lamba que es una
funcin anonima, es para no tener que declara un funcin para cosas simples. Por
exemplo el la definicin arriba del dafault para el campo date tendriamos que hacer
asi.
def _get_now_time(self, cr, uid, context={}):
return time.strftime('%Y-%m-%d %H:%M:%S')
_defaults = {
'start_date': _get_now_time,

lambda es una funcin annima. Se ejecuta como una funcin normal pero solo
para procesar una sola lnea de cdigo. Se le pueden pasar parametros decirle que no
ser as, escribiendo *a.

Ejercicios 19: defaults

Crear en la clase session un campo nuevo llamado active del tipo boolean el mismo
es un campo de nombre reservado por openerp con la funcin automtica de definir
la visibilidad del registro en las consultas.
Vamos tambin atribuir valores por defecto en el campo start_date y cambiar su tipo
de campo de date para datetime. Para esto tenemos que hacer un import en el archivo python de un paquete llamado time que possee funciones de python para tratar
campos del tipo hora. Agreguemos tambien el valor por defecto al campo active
para true.

70

7. Openobject como RAD


7.14 Manejando Colores en el grid
En OpenERP es posible utilizar colores para facilitar la identificacin de situaciones
utilizando los famosos recursos de colorizacin de las lines en el grid. Este recurso se
utiliza de un atributo en las vistas lamado colors donde aplicamos de la siguiente forma siempre debe ser utilizado dentro de la tag tree.
<record id="view_location_tree2" model="ir.ui.view">
<field name="name">stock.location.tree</field>
<field name="model">stock.location</field>
<field name="type">tree</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<tree
colors="blue:usage=='view';darkred:usage=='internal'">
<field name="complete_name"/>
<field name="usage"/>
<field
name="stock_real"
invisible="'product_id' not in context"/>
<field
name="stock_virtual"
invisible="'product_id' not in context"/>
</tree>
</field>
</record>

En el ejemplo cuando el campo usage sea igual a 'view' el color de la linea ser
azul y cuando el campo usage sea 'internal' sea rojo oscuro. Para definir los colores se
utiliza la misma denominacin usada en codigos html que puede ser en la representacin escrita o la representacin tipo hexadecimal #00ff00.
7.14.1 Uso de caracteres especiales en las vistas.
Un cuidado muy importante en la utilizacin de caracteres especiales en las
vistas por ejemplo &, los simbolos de > ,< etc. es que pueden y sern interpretados como xml seguramente generando error en la vista. Por eso es importante siempre
utilizar la tabla de caracteres especiales abajo siempre para evitar errores en las vistas.

71

7. Openobject como RAD


Caracteres acentuados
&aacute; &Aacute;

&acirc;

&Acirc;

&atilde;

&agrave;

&eacute; &Eacute;

&ecirc;

&Atilde;
&Agrave;
&Ecirc;

&iacute;

&Iacute;

&oacute; &Oacute;

&otilde;

&Otilde;

&ocirc;

&Ocirc;

&uacute; &Uacute;

&uuml;

&Uuml;

&ccedil;

&Ccedil;

&ntilde;

&Ntilde;

&auml;

&Auml;

&aring;

&Aring;

&euml;

&Euml;

&grave;

&Egrave;

&iuml;

&Iuml;

&igrave;

&Igrave;

&ouml;

&Ouml;

&ograve;

&Ograve;

&ugrave; &Ugrave; &ucirc;

&Ucirc;

&yacute;

&Yacute;

&yuml;

&Yuml;

&aelig;

&AElig;

&oelig;

&OElig;

&dagger; &Dagger; &scaron;


&THORN
&eth;
;

&Scaron;

&thorn;

&ETH;

&sect;

&fnof;

&szlig;

&micro;

&iexcl;

&iquest;

&icirc;

&Icirc;

Caracteres especiales
&nbsp;
&

&amp;

>

&gt;

<

&lt;

&circ;

&tilde;

&uml;

&cute;

&cedil;

"

&quot;

&ldquo; e &rdquo;

&lsquo; e &rsquo;

&lsaquo; e &rsaquo;

72

7. Openobject como RAD


e

&laquo; e &raquo;

&ordm;

&ordf;

&ndash;

&mdash;
&shy;

&macr;

&hellip;

&brvbar;

&bull;

&para;

&sect;

&spades;

&clubs;

&hearts;

&diams;

Caracteres matemticos y logicos.

&sup1;

&sup2;

&sup3;

&frac12;

&frac14;

&frac34;

>

&gt;

<

&lt;

&plusmn;

&minus;

&times;

&divide;

&lowast;

&frasl;
&permil;

&int;

&sum;

73

7. Openobject como RAD

&prod;

&radic;

&infin;

&asymp;

&cong;

&prop;

&equiv;

&ne;

&le;

&ge;

&there4;

&sdot;

&middot;

&part;

&image;

&real;

&prime;

&Prime;

&deg;

&ang;

&perp;

&nabla;

&oplus;

&otimes;

&alefsym;

&oslash;

&Oslash;

&isin;

&notin;

&cap;

&cup;

&sub;

&sup;

&sube;

&supe;

&exist;

&forall;

&empty;

74

7. Openobject como RAD

&not;

&and;

&or;

&crarr;

&larr; e &rarr;

&uarr; e &darr;

&harr;

&lArr; e &hrrr;

&uArr; e &dArr;

&hArr;

&lceil; e &rceil;

&lfloor; e &rfloor;

&loz;

Ejercicios 20: colorizacin de grid

En la vista tree de la clase session, hagamos que cuando la duracin sea menor que
5 el color de la linea sea #00ff00 y rojo si la duracin sea mayor de 15.

75

7. Openobject como RAD


7.15 Vista tipo Calendario.
En OpenERP tenemos ademas de la vista tree y form tambien tenemos la vista tipo
calendario que nos muestra por fecha las informaciones.
Atributos

Color: nombre del campo para seguimentacin


en colores.

date_start: nombre del campo que contiene la fecha que empieza el evento (date/time)

day_length: cantidad de horas en el dia de trabajo (default:8)

date_stop: nombre del campo que contiene la fecha final del evento tipo (date/time)
OR

Allower elements

date_delay: nombre del campo con la duraccion


del evento.

Solamente field (para definir el label de los eventos en el


calendario.

<calendar string="Ideas" date_start="invent_date" color="inventor_id">


<field name="name"/>
</calendar>

Ejercicios 21: Vista Calendario

Crear una vista nueva tipo calendario para la clase session usando los parametros
abajo:
date_start
campo start_date
date_delay
campo duration
day_length
'1'
color
campo instructor_id
Campo a mostrar
name

Ejercicios 22: campo calculado sumatoria de participante de todas la sessiones del curso

Agregar en la clase cursos un campo calculado que sumen los participantes de todas
la secciones el campo sera llamado 'attendee_count'.
Tambin agregar el campo funcion 'attendee_count' en la clase session que contenga el total de participantes.

76

7. Openobject como RAD

7.16 Vistas del tipo Gantt.


Es usualmente utilizar para mostrar el desarrollo de proyectos.
Atributos

Color: nombre del campo para seguimentacin


en colores.

date_start: nombre del campo que contiene la fecha que empieza el evento (date/time)

day_length: cantidad de horas en el dia de trabajo (default:8)

date_stop: nombre del campo que contiene la fecha final del evento tipo (date/time)
OR

Allower elements

date_delay: nombre del campo con la duraccion


del evento.

Field, level
level es usado para definir el grado del grafico de grantt
atributos de level:
object objeto de openerp que contiene el campo many2one
link nombre del campo del objeto actual que va linkado
con el objeto origen
domain el dominio es usado para filtrar los registros recuperados del objeto arriba mencionado.

<gantt string="Ideas" date_start="invent_date" color="inventor_id">


<level object="idea.idea" link="id" domain="[]">
<field name="inventor_id"/>
</level>
</gantt>

Ejercicios 23: vista gantt

Definir una vista tipo gantt para la clase sessin con los mismos datos de la vista
Calendar, cambiando el attributo color para que utilize course_id

77

7. Openobject como RAD


7.17 Vistas del tipo Graficos (graphs).
Usualmente utilizado para mostrar grficos de estadsticas.
Atributos

Allower elements

Type: tipo del grafico, barra (bar), pizza (pie)

orientacin: horizontal, vertical

Campos a tratar.

Primer campo es la vista del ejel x el 2 es del eje


y el 3 es del eje Z. 2 campos son obligatorios.

Atributo group definido como el group by setedo


para 1 o verdadero.

Operador Atributo que definie la operacin por


cada registro del campo 2 que puede ser +
(suma), * (multiplicacin), ** (exponenciacin),
min y max.

<graph string="Total idea score by Inventor" type="bar">


<field name="inventor_id" />
<field name="score" operator="+"/>
</graph>

Ejercicios 24: Vista Graphs

Definir una vista graphs, tipo bar en la session, que contenga como campo X el
course_id y como eje Y el campo attendee_count sumando los mismos

78

7. Openobject como RAD


7.18 Vistas tipo search (busqueda)
Vistas Search se utilizan para personalizar el panel de bsqueda en la parte superior de
las vistas tree (arbol), y se declaran con el tipo search, con el elemento en el xml
<search>.
Tras definir una vista search con un identificador nico, solamente tenemos que
aadir a la accin la llamada de esta vista con el atributo "search_view_id" como ref
al nombre de la vista search.
<record model="ir.ui.view" id="view_ideia_ideia_search">
<field name="name">view.ideia_ideia.search</field>
<field name="model">ideia.ideia</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Search Ideas">
<group col="6" colspan="4">
<filter string="My Ideas" icon="terp-partner"
name ="my_ideas"
domain="[('inventor_id','=',uid)]"
help="My own ideasA"/>
<field name="name" select="1"/>
<field name="description" select="1"/>
<field name="inventor_id" select="1"/>
<!-- following context field is for illustration only -->
<field name="inventor_country_id" select="1" widget="selection"
context="{'inventor_country': self}"/>
</group>
</search>
</field>
</record>

A travs del atributo context es posible pasar a la vista cuales los filtros seleccionados como estandarte, por ejemplo si queremos que el my ideas ya venga seleccionado se pasa en el contexto un parmetro especial {"search_default_XXX":1}
donde XXX es el nombre del filtro por ejemplo search_default_my_ideasA como el
de arriba en el ejemplo.

Ejercicios 25: Vistas Search (busqueda)

Crear una vista de busqueda (search) para la clase cursos, que tenga un filtro mis
cursos, que filtre atravs del dominio "[('responsible_id','=',uid)]" que el icono utilizado sea "terp-partner". Esto nos resume que va listar en el grid solamente los cursos que el reponsable sea el usuario que registro los cursos y quedo como responsable. Y que tambien que este filtro venja como estandarte seleccionado.

79

7. Openobject como RAD

Ejercicios 26: Vista Search - session

Crear una vista de busqueda (search) para la clase session. Es recomendable utilizar
la vista search para definir los campos de busqueda que tambien se puede utilizar el
atributo select='1' en la vista tree.

Ejercicios 27: Vista Search - course

Editar la vista de cursos, que tenga el dominio de bsqueda de mas un botn, que
filtre los que no son mis cursos. Dejando como estandarte que no quede seleccionado al abrir la bsqueda o sea pasarlo por el context

Todava hablando de sobre las vistas search tenemos otra caracteristicas importante
y yo principalmente considero una de las mas espetaculares funcionalidades de OpenERP, que e la capacidad de agrupar los registros por determinados campos claves,
pero ademas de poder agrupar (group by) podremos hacerlos en diferentes agrupaciones por exemplo agrupar los cursos, por session por responsable, o cambiar el orden
de agrupacin por responsable por curso por session. Esto nos da los medios de analisar las informacines mucho mas fcil. Esto se aplica en la propria vista como los botones que ya lo vimos (filters) pero en vez de utilizar el dominio para filtrar los registros utilizamos el context para definir la agrupacin, para un mejor ordanacin separamos los mismo atraves del atributo group con el nombre group by..
En los filters juntamente con en context podremos formar entonces las agrupaciones de la siguinte forma.
<record model="ir.ui.view" id="view_idea_category_search">
<field name="name">idea.category.search</field>
<field name="model">idea.category</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Ideas Categories">
<group>
<field name="name" string="Category"/>
<field name="parent_id" widget="selection"/>
</group>
<newline/>
<group expand="0" string="Group By...">
<filter string="Parent Category" icon="terp-stock_symbol-selection" domain="[]"
</group>
</search>
</field>
</record>

context="{'group_by':'parent_id'}"/>

Podremos ver que es basicamente el mismo concepto que vimos para filtrar registros por el domains pero vemos que el domain queda vazio y luego en el context pasa-

80

7. Openobject como RAD


mos el parametro group_by con el campo de agrupacin. Podremos notar que utilizamos un tag group con parametro expand=0 que es false para mantener ordenado y
ocupando menos espacio en la pantalla.

Ejercicios 28: Vista Search con filtros de group_by

Editar la vista search de cursos, que tengamos una agrupacin de los registros por
responsable y tambin agrupacin por nombre.

81

7. Openobject como RAD


7.19 Seguridad en los modulos de OpenERP
La seguridad de openerp esta basada en grupo y las permisiones son por tipo de
permisiones por objeto (clase), las permisiones son

perm_read, perm_write,

perm_create, perm_unlink que son respectivamente, permisin de lectura, permision


de escritura (editar), permisin de agregar y permisin para borrar.
En openERP en la creacin de un modulo debemos crear los grupos de permisiones
y luego aplicar las respectivas permisiones de accesso. Para agregar estos datos utilizamos los recursos de serializacin de datos por XML y CSV que permite openerp.
Es importante salientar que el tipo de archivo utilizado para la importacin de los
datos tiene la siguiente regra. Los CSV son datos crudos, y solamente importan datos
de unicamente 1 tabla y su nombre deber ser en nombre del objeto (clase). Vamos
utilizar este archivo para agregar las permisiones de los grupos que son guardados en
el objeto ir.model.access Recuerden que cuando referenciamos un objeto lo pasamos como separador el '.' pero el nombre del objeto en la base de datos por ejemplo
seria ir_model_access.
Los registros en csv en formato de tabla tenemos abajo:
id
access_idea_category_user
access_idea_idea_user
access_idea_vote_user
access_idea_vote_stat_user
access_idea_comment_user
access_report_vote_manager

name
idea.category user
idea.idea user
idea.vote user
idea.vote.stat user
idea.comment user
report.vote manager

model_id:id
model_idea_category
model_idea_idea
model_idea_vote
model_idea_vote_stat
model_idea_comment
model_report_vote

group_id:id
base.group_tool_user
base.group_tool_user
base.group_tool_user
base.group_tool_user
base.group_tool_user
base.group_tool_manager

perm_read

perm_write
1
1
1
1
1
1

perm_create
1
1
1
1
1
1

perm_unlink
1
1
1
1
1
1

1
1
1
1
1
1

Cuando queremos utilizar el csv para agregar datos de las series many2one, one2many etc tenemos que utilizar las reglas abajo para que el ORM consiga encontrar los
datos relacionados, lo que le define como el ORM va identificar los registros va de
nombre de la cabecera del csv con los nombre abajo definidos.
.id,

(=database_id)
base de datos

el numero del ID en la

partner_id,

(=name_search) el ORM utilizar el


name_search para conocer el relacionamiento o sea tenemos que poner el nombre del registro name de la tabla extranjera.

order_line/.id,

(=database_id)
base de datos

order_line/name,

El proprio nombre del registro

order_line/produtct_id/id,

(=xml id) debe ser utilizado el xml id definido


en los xml.

el numero del ID en la

82

7. Openobject como RAD

Otra forma de generar estes datos es utilizando em modulo recorder de openerp.


Tambin es necesario definir los datos de grupos en openerp para eso utilizaremos
los archivos del tipo xml para la importacin de datos ya que el mismo tiene posibilidad de definir registros de multiplas tablas y tambien permite evaluar dados. El nombre del archivo puede ser cualquier nombre ya que para definir en cual clase sera agregados los registros son definidos en el xml.
<?xml version="1.0"?>
<openerp>
<data>
<record model="res.groups" id="base.group_tool_user">
<field name="name">User</field>
<field name="category_id" ref="base.module_category_tools"/>
</record>
<record model="res.groups" id="base.group_tool_manager">
<field name="name">Manager</field>
<field name="category_id" ref="base.module_category_tools"/>
<field name="implied_ids" eval="[(4, ref('base.group_tool_user'))]"/>
</record>
</data>
</openerp>

La estructura es el model se utiliza para definir el nombre de la tabla (clase) el id es


el id-xml definido para el registro, los fields name definimos el nombre del campo y
adentro ponemos el contenidos del campo. Tenemos una expresin eval en el campo
implied_ids as

eval="[(4, ref('base.group_tool_user'))]

que siguiendo la tabla abajo podemos

definir su accin para cada tipo de campo abajo tenemos los codigos utilizados para
los campos tipo many2many.
(0,0,{values})

Vincula a un nuevo registro que debra ser


creado con el valor del diccionario

(1, ID, {values})

Actualiza el registro vinculado con el registro


de id =ID (grava el *values* en el)

(2, ID)

Remove y borra el registro con id=ID vinculado (llama el metodo unlink (borrar) el ID, y
borra el objeto completamente)

(3, ID)

Remove el vinculo con el registro con el id


=ID (borra el relacionamiento entre los 2 objetos pero no borra el objeto relacionado

(4, ID)

Vincula con un registro que ya existe co el ID


especificado.

(5)

Remove todos los vnculos, (tipo usando (3,


ID) para todos los registros vinculados.

(6, 0, [IDS])

Sustituye la lista de IDS vinculados (usando


el (5) y tambien el (4,ids) para cada ID en la
la lista de IDs)

83

7. Openobject como RAD


Para los campos relacionales del tipo one2many la tabla es as:
(0, 0, { values } )

Vincula a un nuevo registro que debe ser


creado con un diccionario pasado en el values.

(1, ID, { values } )

Vinculados con el ID mencionado y sustituye


los valores de estos ID con los valores pasados por el parmetro (values).

(2, ID)

Remove y borra los links con los registros pasados en los ID (llama el metodo unlink con
los ID, y borra los objetos por completo.

Ejemplo:
[ (0, 0, {'field_name': field_value_record1, }), (0, 0, {'field_name': field_value_record2,...})]

Para campos many2one usar el ID para hacer referencia al registro o pasar le False
para borrar la relacin (vinculo).

Ejercicios 29: Seguridad

Crear los archivos de datos para el mtodo de seguridad del Modulo openacademy,
tendriamos 2 grupos openacademy Manager e openacademy user, y que los usuario
no puedan borrar registros.

84

7. Openobject como RAD


7.20 Flujos de trabajos (workflow)
Un workflow de documentos, es una secuencia de estados por los que pasa un documento segn unas condiciones predeterminadas.
Por ejemplo: Un pedido de compra se crea en estado borrador. Alguien lo confirma.
El documento no puede ser enviado al proveedor hasta que sea confirmado. En este
workflow hay 2 estados borrador y confirmado. El documento pasa de uno a otro
cuando alguien lo confirma.
El paso de estado de un documento puede ser automtico segn las condiciones
que se establezcan o puede ser realizado manualmente por un usuario con permisos
para ello.
OpenERP puede mostrar el workflow de cualquier operacin y el estado de un documento siguiendo el workflow. Tambin se puede imprimir el diagrama (grficamente) de estados de un documento.
Objetivos:

descripcin de la evolucin en el tiempo documento

activacin automtica de las acciones, si se cumplen ciertas condiciones

gestin de las funciones de la empresa y los pasos de validacin

gestin de las interacciones entre los diferentes objetos y mdulos

herramienta grfica para la visualizacin de los flujos de documentos

Para entender su utilidad veamos el ejemplo abajo de un wokflow de un pedido


(order)

85

7. Openobject como RAD


Una orden empieza con el estado Draft (borrador), que en un ERP es el estado que
no tiene compromiso alguno por parte del cliente. Luego el usuario confirma el pedido (draft) borrador presionando un botn en el pedido, y el pasa del estado draft para
confirmado:
A continuacin, dos operaciones son posibles:
1. el pedido es enviado o entregue.
2. la orden es cancelada.
Supongamos que una empresa tiene una necesidad que no se han aplicado en OpenERP. Por ejemplo, supongamos que su personal de ventas slo pueden ofrecer descuentos mximo de 15%. Todas las orden que tiene un descuento superior al 15% deben ser aprobados por el gerente de ventas. Esto transformara nuestro workflow en:

Esta modificacin en la logica de ventas, no hace falta poner ni una linea de codigo
python para cambiar el fluxo de trabajo en openerp.
Usando el editor de workflow agregamos la validacin y luego tenemos que agregar el botn de validacin en el formulario de ordenes.
Veremos ahora como definir un workflow y sus definicines.

7.20.1 Definicin de Workflow


Los worflow Tenemos 3 componentes bsicos descrito en su clase. El workflow propriamente dicho, wkf_activity y wkf_transition que son:
wkf_activity son las actividades de workflow que son representados en el grafico arriba como los circulos (nodos).
wkf_transistion Son las transaciones entre los nodos, o sea las arestas.

86

7. Openobject como RAD


7.20.2 Estructura general del xml de un Workflow.
En lo general la estructura de un xml para la definicin del workflow son:
<?xml version="1.0"?>
<openerp>
<data>
<record model="workflow" id=workflow_id>
<field name="name">workflow.name</field>
<field name="osv">resource.model</field>
<field name="on_create">True | False</field>
</record>
</data>
</openerp>

Donde:
id es el identificador de workflow debe ser nico en todo el sistema
name nombre del workflow debe mantener la relacin de nombre usando el
punto como separador.
on_create si True es disparado el workflow automaticamente cuando el resurce.model (clase) es creada.
Esta es la definicin del workflow pero em mismo como vimos arriba esta compuesto por atividades (wkf_activity) y transaciones (wkf_transaction).

7.20.3 Actividades (activity).


Las Wkf_activity son clases que representa los nodos del workflow, cada nodo tiene acciones a ejecutar.
En la declaracion de las actividades podremos tener varias acciones a dispara por
las arestas (transaciones). Los atributos que tenemos son.
split_mode

Como el grafico el split_mode puede disparar varias salidas (transasiones) y puede


dispara 0, una o todas las transacciones esto de define utilizando los operadores.

XOR apenas necesita executa solamente 1 transaccin la primera que


encuentra.

87

7. Openobject como RAD

OR Toma solamente las transaciones validas (0 o mas) en orden secuecial.

AND ejecuta todas las transaciones validas (todas a la vez).

En el modo AND la actividade espera que todos lsa transaciones sean completadas
y dispara todas juntas.
Join_mode

XOR una sola transacin es necesario para seguir la transacin.


AND Espera que todas las transaciones sean validadas para seguir la transacin.
KIND
El tipo de actividad puede tener varios valores:
DUMMY No hace nada (default)
FUNCTION Ejecuta una funcin declarada en el action.
SUBFLOW Ejecuta un sub-workflow
ACTION
La accin idica el mtodo a ejecutar cuando un elemento de trabajo entra en esta
actividad. El Mtodo debe ser definido en un objeto que pertenece a este flujo de trabajo.
flow_start
Indica que es el inicio del flujo de trabajo.
flow_stop
Indica el final del flujo de trabajo.

88

7. Openobject como RAD


wkf_id
Indica a cual workflow esta actividad pertenece.
Definicin de las activity (actividades) en los archivos XML.
En lo general la estructura del xml es as.
<record model="workflow.activity" id="''activity_id''">
<field name="wkf_id" ref="''workflow_id''"/>
<field name="name">''activity.name''</field>::
<field name="split_mode">XOR | OR | AND</field>
<field name="join_mode">XOR | AND</field>
<field name="kind">dummy | function | subflow | stopall</field>
<field
<field
<field
<field
</record>

name="action">''(...)''</field>
name="signal_send">''(...)''</field>
name="flow_start">True | False</field>
name="flow_stop">True | False</field>

Los 2 primeros atributos son mandatarios (wkf_id y name).

7.20.4 Transiciones
Transiciones de flujo de trabajo son las condiciones que deben cumplirse para pasar de una actividad a la siguiente. Estn representados por las flechas de un solo sentido que unen dos actividades.
Las condiciones son de diferentes tipos:

funcin de satisfacer el usuario

pulsado el botn en la interfaz

final de un subflujo a travs de una actividad seleccionada de subflujo

Los roles y las seales son evaluados antes de la expresin. Si a una funcin o una
seal que es falso, la expresin no ser evaluado.
Las pruebas de transicin no puede escribir valores en los objetos.
act_from
Actividad origen.

act_to
Actividad Destino.

89

7. Openobject como RAD


Condition
Expresin que debe ser cuprida para que ocurra la transacin.
Signal
Cuando la operacin de transacin debe ocurrir al ser presionado un botn en el
cliente, el signal prueba el nombre del botn presionado.
Si la seal es NULL, no se espera el presionado de un botn para seguir la transaccin.
Role_id
La permisin que el usuario tiene que tener para permitir la transaccin.
Definicin de las transacciones en el XML.
La estructura general del record xml.
<record model="workflow.transition" id="transition_id">
<field name="act_from" ref="activity_id'_1_'"/>
<field name="act_to" ref="activity_id'_2_'"/>
<field name="signal">(...)</field>
<field name="role_id" ref="role_id'_1_'"/>
<field name="condition">(...)</field>
<field name="trigger_model">(...)</field>
<field name="trigger_expr_id">(...)</field>
</record>

Los parmetros obligatorios son act_from y act_to de las transiciones.

7.20.5 Creando un workflow


Vamos crear un workflow simples simplemente mudando el estatus de un objeto.
Esto seria por ejemplo tener un campo llamado state con los estados (new, assigned,
negotiation, won, lost.

Definiendo el campo stage en un objeto


Lo primero es crear un campo llamado stage que va tener algunos estagios de una
negociacin. Vemos la definicin de la columna.

90

7. Openobject como RAD


_columns = {
...
'state': fields.selection([
('new','New'),
('assigned','Assigned'),
('negotiation','Negotiation'),
('won','Won'),
('lost','Lost')], 'Stage', readonly=True),
}

Luego vamos a definir los mtodos que cambian cada estado. Estos metodos sern
llamados por los botones en la interface de usuario y manejados por el workflow.
def mymod_new(self, cr, uid, ids):
self.write(cr, uid, ids, { 'state' : 'new' })
return True
def mymod_assigned(self, cr, uid, ids):
self.write(cr, uid, ids, { 'state' : 'assigned' })
return True
def mymod_negotiation(self, cr, uid, ids):
self.write(cr, uid, ids, { 'state' : 'negotiation' })
return True
def mymod_won(self, cr, uid, ids):
self.write(cr, uid, ids, { 'state' : 'won' })
return True
def mymod_lost(self, cr, uid, ids):
self.write(cr, uid, ids, { 'state' : 'lost' })
return True

Declarando el workflow en el xml


Necesitamos crear 3 tipos de record definidos en el archivo mymod_workflow.xml.
1. Declaracin del workflow propiamente dicho.
<record model="workflow" id="wkf_mymod">
<field name="name">mymod.wkf</field>
<field name="osv">mymod.mymod</field>
<field name="on_create">True</field>
</record>

2. Declaracin de las activity (actividades)


Estos definen las acciones que se deben ejecutar cuando el flujo de trabajo llega a un
estado particular
<record model="workflow.activity" id="act_new">
<field name="wkf_id" ref="wkf_mymod" />
<field name="flow_start">True</field>
<field name="name">new</field>
<field name="kind">function</field>
<field name="action">mymod_new()</field>
</record>
<record model="workflow.activity" id="act_assigned">
<field name="wkf_id" ref="wkf_mymod" />
<field name="name">assigned</field>
<field name="kind">function</field>
<field name="action">mymod_assigned()</field>
</record>

91

7. Openobject como RAD


<record model="workflow.activity" id="act_negotiation">
<field name="wkf_id" ref="wkf_mymod" />
<field name="name">negotiation</field>
<field name="kind">function</field>
<field name="action">mymod_negotiation()</field>
</record>
<record model="workflow.activity" id="act_won">
<field name="wkf_id" ref="wkf_mymod" />
<field name="name">won</field>
<field name="kind">function</field>
<field name="action">mymod_won()</field>
<field name="flow_stop">True</field>
</record>
<record model="workflow.activity" id="act_lost">
<field name="wkf_id" ref="wkf_mymod" />
<field name="name">lost</field>
<field name="kind">function</field>
<field name="action">mymod_lost()</field>
<field name="flow_stop">True</field>
</record>

3. Definimos finalmente las transacciones (transition).


Estos definen las posibles transiciones entre los estados de flujo de trabajo.
<record model="workflow.transition" id="t1">
<field name="act_from" ref="act_new" />
<field name="act_to" ref="act_assigned" />
<field name="signal">mymod_assigned</field>
</record>
<record model="workflow.transition" id="t2">
<field name="act_from" ref="act_assigned" />
<field name="act_to" ref="act_negotiation" />
<field name="signal">mymod_negotiation</field>
</record>
<record model="workflow.transition" id="t3">
<field name="act_from" ref="act_negotiation" />
<field name="act_to" ref="act_won" />
<field name="signal">mymod_won</field>
</record>
<record model="workflow.transition" id="t4">
<field name="act_from" ref="act_negotiation" />
<field name="act_to" ref="act_lost" />
<field name="signal">mymod_lost</field>
</record>

Luego tenemos que incluir este xml en el archivo de declaracin de modulo __openerp_.py en la sessin update_xml.
Ademas tenemos que agregar en la vista de modulo el grupo de botones que reciben el
signal de workflow para definir el titulo del mismo.
<separator string="Workflow Actions" colspan="4"/>
<group colspan="4" col="3">
<button name="mymod_assigned" string="Assigned" states="new" />
<button name="mymod_negotiation" string="In Negotiation" states="assigned" />
<button name="mymod_won" string="Won" states="negotiating" />
<button name="mymod_lost" string="Lost" states="negotiating" />
</group>

92

7. Openobject como RAD


Ejercicios 30: Workflow

Crear un workflow para la clase session, que contenga los estados draft, confirm y
done.

Ejercicios 31: Validacin en el workflow y tirado de error

Agregar al workflow que solamente permita cambiar al estado a confirmado si la fecha de la seccion sea igua o mayor que da fecha actual.
Nota: para ejecutar un exception en openerp usamos:
raise osv.except_osv( 'Error !', 'mensaje' )

93

7. Openobject como RAD


7.21 Wizard (procesos).
Wizard describe una secuencia interactiva entre el cliente y servidor, un ejemplo tipico del proceso wizars.
1. Una ventana es enviada al cliente (un formulario a completar)
2. el cliente envia de vuelta al server los dados completados en el cliente.
3. El server recibe los resultado y ejecuta una funcin y posiblemente envia otra
ventana al cliente.
Para crear un wizard haremos:

Creamos el archivo con la definicin de los datos e un archivo .py

E wizard es usualmente definido en una nueva carpeta dentro

del modulo principal llamada wizard y dentro en archivo de


definicin .py quedando en /tumodulo/wizard/tu_modulo_wizard.py.

En la definicin del modulo __init__.py debemos agregar el import wizard.

Declarar tu wizard en la base de datos con la definicin de su vista.

Para desarrollar un wizard es basicamente la misma forma de desarrollar un abm


que ya lo hicimos, solo que en vez de ser heredado de la clase osv.osv va heredado de
la clase osv.osv_memory.
La gran diferencia es que los wizard trabajan en la memoria o sea no hay objetos
guardados en la base de datos. Es usado para solicitar parametros en reportes o en procesamientos en lote.
Vamos definir primeramente los 3 componentes de un wizard con el archivo de clase .py, el XML de la vista y el action.
class config_compute_remaining(osv.osv_memory):
_name='config.compute.remaining'
def _get_remaining(self,cr, uid, ctx):
if 'active_id' in ctx:
return self.pool.get('project.task').browse(cr,uid,ctx['active_id']).remaining_hours
return False
_columns = {
'remaining_hours' : fields.float('Remaining Hours', digits=(16,2),),
}
_defaults = {
'remaining_hours': _get_remaining
}
def compute_hours(self, cr, uid, ids, context=None):
if 'active_id' in context:
remaining_hrs=self.browse(cr,uid,ids)[0].remaining_hours
self.pool.get('project.task').write(cr,uid,context['active_id'],
{'remaining_hours' : remaining_hrs})
return {
'type': 'ir.actions.act_window_close',
}
config_compute_remaining()

94

7. Openobject como RAD


Notemos que es idntico en su estructura a la definicin de un clase de objeto que
son poseen persistencia en la base de datos. Veamos las diferencias.
Primeramente notemos que en la definicin de la clase es deriva de osv.osv_memory y no de osv.osv.
El restante es basicamente una clase normal de openerp lo unico que notamos que
es diferente es el codigo.
def compute_hours(self, cr, uid, ids, context=None):
if 'active_id' in context:
remaining_hrs=self.browse(cr,uid,ids)[0].remaining_hours
self.pool.get('project.task').write(cr,uid,context['active_id'],
{'remaining_hours' : remaining_hrs})
return {
'type': 'ir.actions.act_window_close',

Este codigo es definido para la accion del usuario si el mismo da un OK o un


Cancel en el wizard.
Luego definimos el XML de la vista:
<record id="view_config_compute_remaining" model="ir.ui.view">
<field name="name">Compute Remaining Hours </field>
<field name="model">config.compute.remaining</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Remaining Hours">
<separator colspan="4" string="Change Remaining Hours"/>
<newline/>
<field name="remaining_hours" widget="float_time"/>
<group col="4" colspan="4">
<button icon="gtk-cancel" special="cancel" string="Cancel"/>
<button icon="gtk-ok" name="compute_hours" string="Update" type="object"/>
</group>
</form>
</field>
</record>

En la vista lo que notamos de diferente so los atributos button en rojo que justamente hacen referencia a la clase mas arriba que atravs del boton Update llama el
metodo compute_hours, y si se presiona cancel lama la tag special que cancela el wizard y destruye todo el objeto en memoria.
Y no menos importante tenemos que definir el action que hace el llamado al wizard.
<record id="action_config_compute_remaining" model="ir.actions.act_window">
<field name="name">Compute Remaining Hours</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">config.compute.remaining</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>

Tambin lo mas notorio de diferencia en el action es el atributo target que pasamos


new. O sea va llamar el wizard en una ventana nueva.

Ejercicios 32: Wizard

Crear un wizard que agregue en la clase session los participantes.

95

7. Openobject como RAD

Ejercicios 33: Wizard - correccion de metodo

Corregir el wizard llamado por el men principal del modulo que genera error.

Ejercicios 34: Mejorando funcionamiento del wizard

En el Wizard creamos que el mismo sea posible ser llamado cuando seleccionamos
varios registro o apenas 1 registro de la clase session cuando seleccionado varios registros en nos trae la sessin de primer registro seleccionado en el tree, haora vamos
implementar que verifique si tenemos varios registros seleccionados y ocultamos
(atributo invisible) el campo session en la vista de este wizard.

96

7. Openobject como RAD


7.22 Dashboard (estadstica)
Dashboard en una forma de tenecer accesso a Estadstica de forma rpida y de diferentes fuentes, en lo general openerp es muy bueno en mostrar informaciones ya que
con las busquedas, filtros y groupby que las vistas poseen deja los datos muy transparente al usuario ya el dashboard es la forma de traer estos datos en apenas 1 pantalla y
de fcil acceso. Abajo tenemos una muestra de un dashboard de ventas.

Tenemos en este dashboard los presupuestos lanados, las ventas por mes valorizado, un grfico de ventas por vendedor en los ltimos 90 das y un grfico de ventas
por cliente en los ltimos 90 das.
En los dashboard vamos utilizar mucho el atributo domain de la definicin de la
vista. En realidad del dashboard es basicamente la llamada de pantallas que ya existe
definida en el modulo solamente pasando algunos filtros a los mismos.
Por ejemplo los presupuestos mostrado en el dashboard es la misma vista que sales
order aplicando los filtros de que traiga apenas los que son presupuestos (darft) y que
sean del usuario conectado en el caso administrador. Veamos en la pantalla abajo.

97

7. Openobject como RAD

As vemos que tengo seleccionados en la vista los presupuestos y vendedor filtrados. Que nos trae los mismos 3 registros.
De igual forma que todas las vistas en openerp es necesario definir las acciones de
las vistas para que sean llamadas. En el archivo xml haramos esta actin asi:
<record id="action_quotation_for_sale" model="ir.actions.act_window">
<field name="name">My Quotations</field>
<field name="res_model">sale.order</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('state','=','draft'),('user_id','=',uid)]</field>
<field name="view_id" ref="sale.view_order_tree"/>
</record>

En esta actin tenemos el domains que pasamos que sean state=draft y que
user_id=uid que es el usuario conectado en el sistema, luego pasamos el view_id
como referencia sale.view_order_tree o sea va buscar la vista en el modulo sale con
el nombre view_order_tree luego se aplicar el dominio que pasamos por la nueva actin.
Si utilizamos las vistas que ya se encuentran en el modulo y no lo vamos a pasar
ningn parmetro de filtro podremos llamar diretamente en la definicin del dashboard.
<record id="board_sales_manager_form" model="ir.ui.view">
<field name="name">board.sales.manager.form</field>
<field name="model">board.board</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Sales Manager Dashboard">
<board style="2-1">
<column>
<action name="%(sale.action_quotation_for_sale)d" string="Quotations" creatable="true"/>
<action name="%(sale.action_view_sales_by_month)d" string="Sales by Month"/>
</column>
<column>
<action name="%(sale.action_sales_by_salesman)d" string="Sales by Salesman in last 90 days"/>
<action name="%(sale.action_sales_by_partner)d" string="Sales per Customer in last 90 days"/>
<action name="%(sale.action_sales_product_total_price)d" string="Sales by Product's Category in
last 90 days"/>
</column>
</board>
</form>
</field>
</record>

98

7. Openobject como RAD


Esta es la definicin de un vista tipo dashboard la parte diferente de las otras vistas
es esta:
<form string="Sales Manager Dashboard">
<board style="2-1">
<column>
<action name="%(sale.action_quotation_for_sale)d" string="Quotations" creatable="true"/>
<action name="%(sale.action_view_sales_by_month)d" string="Sales by Month"/>
</column>
<column>
<action name="%(sale.action_sales_by_salesman)d" string="Sales by Salesman in last 90 days"/>
<action name="%(sale.action_sales_by_partner)d" string="Sales per Customer in last 90 days"/>
<action name="%(sale.action_sales_product_total_price)d" string="Sales by Product's Category in last 90
days"/>
</column>
</board>
</form>

Donde board style es la definicion de como sera el layout del dashboard, el dashboard puede tener 3 columnas, pero como en las vistas puede juntar 2 colunmas. Siguiendo mas o menos el siguente layout.

O sea tenemos como este tag las opciones abajo en la misma orden del dibujo arriba:
style=1 junta todas las 3 columnas mostrandos las vistas em 1 columna
style=1-1 divide em 2 columnas
style=1-1-1 3 columnas en este caso es importante que en la definicin de la
vista tengamos 3 divisiones de la tag column ya que si no lo tenemos no dara error la
vista.
Style= 1-2 una columna menor y outra columna ocupando 2 epacios.
Style=2-1 lo mismo de arriba pero alreves.
Column definimos el contenido de cada columna que en nuestro ejemplo es 2
columnas y teriamos presupuestos y ventas por mes en una columna a la izquierda y 3
vistas ventas por vendedor a 90 dias, clientes atendidos en 90 dias y categorias de produtos vendidos en 90 dias.
Luego tenemos el actin en que decimos cuales las vistas vamos llamar, vean que
las vistas llamados normamente por el nombre ya que no es nomalmente conocido el
99

7. Openobject como RAD


ID para llamar la vista por el nombre pasamos el nombre entre %(_nombre_)d el
_nombre_ debemos pasar el nombre del modulo punto <.> el nombre de la vista luego
pasamos un string con un descripcin que va quedar arriba de la vista, tambien es posible pasar la tag creatable="true" para tener un boton de crear un registro nuevo en
esta vista.
Luego tenemos que hacer el actin del dashboard y el menu para su llamada:
<record id="open_board_sales_manager" model="ir.actions.act_window">
<field name="name">Sales Manager Dashboard</field>
<field name="res_model">board.board</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="board_sales_manager_form"/>
</record>
<menuitem id="board.menu_dasboard" name="Dashboard" sequence="0" parent="base.next_id_64"/>
<menuitem action="open_board_sales_manager" icon="terp-graph" id="menu_board_sales_manager"
parent="board.menu_dasboard" sequence="0" groups="base.group_sale_manager"/>

En resumen nuetro vista tipo dashboard completa quedaria asi:


<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="board_sales_manager_form" model="ir.ui.view">
<field name="name">board.sales.manager.form</field>
<field name="model">board.board</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Sales Manager Dashboard">
<board style="2-1">
<column>
<action name="%(sale.action_quotation_for_sale)d" string="Quotations"
creatable="true"/>
<action name="%(sale.action_view_sales_by_month)d" string="Sales by Month"/>
</column>
<column>
<action name="%(sale.action_sales_by_salesman)d" string="Sales by Salesman in last 90
days"/>
<action name="%(sale.action_sales_by_partner)d" string="Sales per Customer in last 90
days"/>
<action name="%(sale.action_sales_product_total_price)d" string="Sales by Product's
Category in last 90 days"/>
</column>
</board>
</form>
</field>
</record>
<record id="open_board_sales_manager" model="ir.actions.act_window">
<field name="name">Sales Manager Dashboard</field>
<field name="res_model">board.board</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="board_sales_manager_form"/>
</record>
<menuitem id="board.menu_dasboard" name="Dashboard" sequence="0" parent="base.next_id_64"/>
<menuitem action="open_board_sales_manager" icon="terp-graph" id="menu_board_sales_manager"
parent="board.menu_dasboard" sequence="0" groups="base.group_sale_manager"/>
<record id="action_quotation_for_sale" model="ir.actions.act_window">
<field name="name">My Quotations</field>
<field name="res_model">sale.order</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('state','=','draft'),('user_id','=',uid)]</field>
<field name="view_id" ref="sale.view_order_tree"/>
</record>
</data>
</openerp>

100

7. Openobject como RAD


Tambin es necesario definir en las dependencias de los modulos tener mencionado
que vamos utilizar el modulo board, como dependencia de nuestro modulo editando el
archivo __openerp__.py y en la directiva depends agregar el modulo board asi:
"depends": ["base", "board"],
En este caso el modulo depende de base, y board.

Ejercicios 35: Dashboard

Crear un Dashboard para el modulo openacademy mostrando las vistas asociadas a


los actions act_session_graph, act_course_list y act_session_calendar.

101

7. Openobject como RAD


7.23 Traduccin de modulos oficiales.
Todas estas traducciones se mantienen mediante la herramienta de traduccin colaborativa que proporciona Launchpad:
https://translations.launchpad.net/openobject
Es mejor hacer las traducciones sobre launchpad pues ests son posteriormente empaquetadas en las prximos lanzamientos de OpenERP.
Si urge disponer en nuestra instalacin la mejor traduccin que est en launchpad
sin esperar el prximo lanzamiento de OpenERP, se puede descargar el archivo po
desde launchpad (el sistema de launchpad lo enviar por email al cabo de unos minutos), dejarlo en la carpeta i18n del mdulo y reiniciar el servidor con la opcin -u
nombe_modulo (o seleccionar el mdulo de la lista de mdulos y hacer una actualizacin).

7.23.1 Traducciones de los mdulo sde addons-extra y addons-community.


La gran mayoria de estos mdulos no se encuentra en launchpad ya que no fueron
subidas para el sistema de traducciones de launchpad.
El procedimiento es el siguiente.
Se extrae desde OpenERP con el men Tools/Administracin/Traducciones/Importa -Exporta/Exporta un archivo de traduccin, seleccionando un mdulo en concreto,
idioma ingls y el formato .po.
El archivo sin traducir se guarda con extensin pot (nombre_modulo.pot) en la carpeta i18n del mdulo (si no existe la carpeta i18n debe crearse).
El mismo archivo se copia en la misma carpeta con el nombre es_ES.po (espaol
de Espaa), es_PY.po (espaol paraguay), ... y se traduce con algn programa de escritorio, por ejemplo el poedit que est en cualquier distribucin Linux.
Por ejemplo, con el mdulo account_reverse se debera tener la siguiente estructura
de carpetas y archivos:
account_reverse/i18n/account_reverse.pot
account_reverse/i18n/es_ES.po

102

7. Openobject como RAD


account_reverse/i18n/es_PY.po
Si se es miembro del grupo commiter de OpenERP el archivo po traducido se puede subir al repositorio addons-extra.
Todo el mundo puede subirlos en el repositorio addons-community.

7.23.2 Para programadores: Traduccin automtica de textos incluidos dentro


de cdigo Python

En OpenERP se utiliza la sintaxis al estilo gettext con _(texto) para marcar el


texto a traducir. El problema que tiene OpenERP a diferencia del procedimiento habitual de gettext es que como se trata de un protocolo sin estado, el idioma de la traduccin puede ser distinto cada vez que se llama la funcin. Por esto, a diferencia de la
mayora de aplicaciones de escritorio, no se puede establecer el idioma del usuario de
forma global.
Afortunadamente, en OpenERP tenemos la variable context en casi todas las funciones y ah dentro acostumbra a estar el idioma del usuario. Es precisamente esto lo
que utiliza OpenERP para las traducciones, pero tiene que hacer un pequeo hack y
tenemos que declarar la clase _.
Es por esto, que en cualquier fichero .py que tengamos que traducir algo debemos
hacer el import correspondiente:
from tools.translate import _

Adems, debemos asegurarnos que dnde estn las llamadas a _, est disponible
la variable context. Es decir si tenemos algo del estilo:
def check(self, values):
if not values.get('field'):
raise osv.except_osv(_("Error"), _("Error!"))
def create(self, cr, uid, values, context=None):
self.mycheck(values)

103

7. Openobject como RAD


No se va a traducir!! Tenemos que pasar el context como parmetro:
def check(self, values, context):
if not values.get('field'):
raise osv.except_osv(_("Error"), _("Error!"))
def create(self, cr, uid, values, context=None):
self.mycheck(values, context)

A parte de esto comentar que _() debe englobar slo texto:


_("Value: %s") % valor

y no
_("Value: %s" % valor)

Ejercicios 36: Traduccin

Hacer todo lo necesario para que nuestro modulo openacademy este totalmente al
idioma espaol paraguay.

104

7. Openobject como RAD


7.24 Reportes
7.24.1 Deseador de reportes OpenOffice (libreoffice).
El diseador de informes con OpenOffice.org es un rpido y potente editor de informes para OpenERP. El objetivo fundamental es que cualquier usuario pueda crear y
modificar fcilmente informes para OpenERP. Se integra en el servidor OpenERP
para aadir nuevos informes o modificar con facilidad (agregando nuevos campos, bucles, expresiones, etc.), los informes existentes.
Caractersticas:
Los informes se disean bajo OpenOffice y se pueden usar desde OpenERP sin tener instalado OpenOffice ni en el PC del cliente, ni en el servidor.
Primero se debe instalar una extensin (o plugin) en OpenOffice. El plugin de OpenOffice conecta con OpenERP, lo que permite aadir distintos objetos desde OpenERP:

Campos

Bucles de listas

Idiomas

Expresiones Python

Imgenes, etc.

Soporta la importacin de archivos .RML por lo que es compatible con el sistema


de informes antiguos de TinyERP. Tambin es capaz de exportar archivos en
formato .RML.
El plugin funciona en la mayora de versiones actuales de OpenOffice.
El comportamiento por defecto es producir un .PDF en OpenERP de acuerdo al documento seleccionado. El usuario slo necesita tener instalado un lector de documentos PDF para visualizarlo o imprimirlo.

105

7. Openobject como RAD


Pero el sistema tambin permite crear plantillas de OpenOffice y abrir automticamente OpenOffice cuando el usuario quiera editar un documento con datos (como
plantillas de fax, cartas,...). En este caso el usuario s necesitara tener instalado OpenOffice en su PC.
El plugin de OpenOffice conecta con el servidor OpenERP usando el protocolo
XML-RPC por lo que se puede disear un informe desde cualquier ordenador con acceso a internet.
Los informes se pueden guardar en el sistema de ficheros, como un informe normal
de OpenERP, o en la base de datos, para ser un informe especfico de la instalacin.
La imgenes se almacenan en el .RML por lo que no hay que preocuparse cmo se almacenan las mismas.

Instalacin
El plugin de diseo de informes de LibreOffice es muy fcil de instalar y utilizar.
Para su instalacin, lo primero que se debe hacer es descargar el plugin desde el propio OpenErp una vez instalado el modulo base_report_designer que esta en las categorias Extra en Openerp.

Luego nos vamos en el menu Settings Personalizacin Informes Deseador


de reportes. Al entrar en este menu nos dara la opcin de descargar la extencin para
libreoffice.

106

7. Openobject como RAD

El fichero descargado est comprimido en formato .zip y empaqueta los archivos


Addons.xcu y OpenERPReport.py, y una serie de iconos que sern los que se muestren en OpenOffice una vez instalado el plugin. La forma ms sencilla de instalarlo es
utilizando la administrador de extensiones de LibreOffice Writer.
Para ello se debe abrir el men Herramientas / Administrador de extensiones...

A continuacin se debe hacer clic en el botn Agregar... Tambin es posible hacer


clic con el botn derecho del ratn y aparecer la opcin de Agregar...

107

7. Openobject como RAD

Una vez agregado el complemento nos dejara disponible, un men y tambin una
barra de herramientas.

Para empezar a trabajar en un informe existente de OpenERP, lo primero que hay


que hacer es conectarse al servidor OpenERP. Se puede acceder a esta funcionalidad
haciendo clic sobre el botn

de la barra de herramientas, o mediante el men Ope-

nERP Report / Server Parameters. Aparecer una ventana que pedir que se introduzca
la direccin URL del servidor, se seleccione la base de datos a la que se desea conectar, la identificacin del usuario y su contrasea. Abajo las Imagines primero de conexin y luego de base de datos y el usuario y contrasea.

108

7. Openobject como RAD

Se acepta y si todo va correctamente, debera aparecer una ventana como abajo que
informa de la posibilidad de realizar el informe en el documento actual.

Abrir un nuevo informe (men Open a new report)


Una vez conectado al servidor, se debe abrir un objeto sobre el que trabajar. Para
abrir este dialogo hay que hacer clic sobre el men Open ERP Report / Open a new report . Se abrir una ventana como la mostrada a continuacin, que permitir seleccionar el objeto de una lista de objetos disponibles en el servidor OpenERP.
Mostrando la lista de modulos y/o objetos disponibles como abajo:

Por ejemplo seleccionamos idea Vote. Esto lo que hace en realidade es agregar algunas propiedades en el documento, vemos esto en la propriedades del documento
una vez seleccionado.
109

7. Openobject como RAD


Yendo al menu archivos propriedades nos abre la ventana abajo moviendo se en
la pestaa -parametros personalizados vemos que agrega el nombre del objeto en el
campo 4.

Aadir un bucle (men Add a loop)


Esta funcin se utiliza para crear una declaracin repeatIn en el informe de OpenERP. Est disponible en el men Open ERP Report / Add a loop, o en la barra de
men, haciendo clic sobre el botn

.Haciendo clic sobre el mismo, se abrir

una nueva ventana como la mostrada abajo, que pedir que se seleccione un objeto
para realizar el bucle, el campo sobre el que realizar el bucle, el nombre de la variable
y el nombre mostrado en el informe.

110

7. Openobject como RAD


Aadir un campo (men Add a field)
Esta funcin se utiliza para aadir la declaracin de un campo en un informe de
OpenERP. Cuando se genere el informe, el campo se sustituir por su valor. Este dilogo est disponible haciendo clic sobre el men Open ERP Report / Add a field, o en
la barra de men, simplemente haciendo clic sobre el botn

Esta ventana solicita el nombre de la variable utilizada en la declaracin repeatIn y


el nombre del modelo relacionado con la variable en el campo Variable. En el campo
Variable Fields, muestra los campos disponibles de la variable y modelo seleccionado
anteriormente. Y en el campo Displayed name, solicita el nombre que debe mostrarse
para la variable seleccionada. Despus de hacer clic en el botn Ok, se obtiene un objeto de campo como el mostrado abajo

111

7. Openobject como RAD

112

7. Openobject como RAD

8 RESOLUCIN DE LOS EJERCICIOS.


Ejercicios 1
Crear la carpeta de un nuevo modulo llamado openacademy. Creando los archivos
__init__.py y __openerp__.py. El modulo propuesto es para control de entrenamiento trabajando con 3 tablas, Cursos (course), seccines (session) y participantes(attendee).

Creamos la estructura de carpetas para el modulo openacademy que debra estar


dentro de la carpeta server/bin/addons/ y ai creamos la carpeta openacademy:
$mkdir /opt/openerp/v6.1/openerp-br/server/bin/addons/openacademy
Luego tenemos que crear el archivo __init__.py. Como explicado este archivo va
contener los import de todos los codigo en python que tiene nuestro modulo que en
este momento tendriamos apenas el openacademy.py que todavia no lo creamos pero
sera el archivo python principal de este modulo. Es importante que todos los archivos
python tenga como primera linea lo siguiente.
# -*- coding: utf-8 -*El Ser-quilla si es obligatorio que en python es una sentensa que dice que el cdigo va contener caracteres utf-8 o sea permitir que adendro del archivo contenga caracteres acentuados. n etc.
__init__.py
#-*- coding: utf-8 -*# Aqu deben declararse los imports de TODOS los archivos py que contenga
# nuestro modulo, para que puedan ser compilados por openERP y as procesados.
# vean

que no es necesario declarar la extensin del archivo python

import openacademy

Creamos tambin el archivo __openerp__.py que es el archivo descriptor de nuestro modulo y los archivos que contengan los dados (registros) del sistema, recordando
que incluso el menu, y las vistas, son registros dentro de la base de datos, esto nos
113

8. RESOLUCIN DE LOS EJERCICIOS.


muestra que OpenERP es un sistema que tiene todo el sistema dentro de la base de datos.

__openerp__.py
{

# Este es el archivo responsable por la descripcin del modulo


# Los dados aqu presente sern mostrado en las opciones de importacin
# del modulo en OpenERP
"name": "Open Academy",
"version": "1.0",
# Dependencias del modulo
"depends": ["base"],
"author": "Fadel Damen Schreiner",
"category": "Academy",
# Aqu debe escribirse una breve descripcin de lo que hace el mdulo.
"description": """
Open Academy Modulo para mantenimiento de entrenamiento:
- Cursos
- Secciones
- Participantes (registro)
""",
# Aqu se declaran todos los xml que contienen la definicin de los menus,
# acciones y vistas que utilizan el mdulo.
'update_xml': ['openacademy_view.xml',
],
'demo': [],
'installable': True,
'active': False,
'certificate': False,

114

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 2
Crear el archivo openacademy_view.xml y crear la referencia al men para nuestro modulo.
Nos referimos a un men principal que no este adentro de ningn otro menu del sistema.

Creamos dentro de la carpeta raiz de nuestro modulo mas 1 archivo el openacademy_view.xml, que va ser el archivo principal de la vista de nuestro modulo, el mismo
siempre que utilizado debe estar declarado en el descriptor del modulo com ya lo hicimos en el ejercicio anterior su declaracin. En principio un menu tambien debe ser declarado adentro del archivo de la vista.
openacademy_view.xml
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<!--Punto de men padre (del mdulo)-->
<menuitem id="menu_main_openacademy" name="OpenAcademy" />
</data>
</openerp>

115

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 3
Definir una nueva clase para la definicin de la class openacademy_course con los campos
name y descripcin.

Creamos dentro de la carpeta openacademy el archivo principal de modulo openacademy el archivo ya lo hemos declarado en el __init__ del modulo y ahora solo nos
falta crear dicho archivo.
openacademy.py
# -*- coding: utf-8 -*##########################################
# OpenERP, modulo creado por: Fadel Damen Schreiner
#from carpeta import archivo
#(archivo.clase)
##########################################
from osv import osv
from osv import fields
class openacademy_course(osv.osv):
_name = 'openacademy.course'
#_table = 'nombre_nuevo'
_columns = {
# 'nombre campo':'valor'
# 'nombre de los campos' : 'objeto de tipo de datos, su etiqueta
# y sus caractersticas de tamao, etc. en la base de datos'
'name' : fields.char('Nombre', size=128),
'descripcion' : fields.text('Descripcion')
}
#
#
#
#

Aqu se crea una instancia y sta al cargarse crea la base de datos


Despus debe agregarse el import al __ini__.py y despus se para el
servidor, se actualiza el mdulo con
python openerp-server.py -c openerp-server-ctp-openerp6.conf --update=openacademy

116

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 4
Definir los menus hijos del menu principal creado anteriormente llamado openacademy.
Crearemos 2 entradas una lista de todos los cursos y la opcin de agregar y modificar nuevos cursos eso implica en crear las vistas form y tree como tambien crear la accin a los
menus.

Lo mas importante en la creaccin de la vista es mantener organizado los nombre


de las vistas seguiendo esta idiologia.
Tenemos las vistas (view) y accines (action) al dar nombre a los ID nos basamos
los nombre utilizando primeramente el tipo de la vista + nombre_del_modulo+nombre_del_elemento. Los nombres de los elementos son ahora tree y form.
Abajo tenemos 2 vistas (tree y form) id="view_openacademy_course_tree" y
id="view_openacademy_course_form" y tambin tenemos el id de nuestra action
id="view_openacademy_course_form"
openacademy_view.xml
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<menuitem id="menu_main_openacademy" name="OpenAcademy" />
<record model="ir.ui.view" id="view_openacademy_course_tree">
<field name="name">view.openacademy.course.tree</field>
<field name="model">openacademy.course</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Course">
<field name="name" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_openacademy_course_form">
<field name="name">view.openacademy.course.form</field>
<field name="model">openacademy.course</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Course">
<field name="name" select="1"/>
<field name="description" select="1"/>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_openacademy_course_form">
<field name="name">Courses</field>
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<!--<field name="limit">100</field>-->
</record>
<menuitem id="menu_action_openacademy_course_form" name="Courses" parent="menu_main_openacademy"
action="action_openacademy_course_form" />
</data>
</openerp>

Como resultado tenemos las pantallas siguientes:

117

8. RESOLUCIN DE LOS EJERCICIOS.


Cursos Tree:

Cursos form:

Aqui vemos que la pantalla no esta bien ordenada, o sea queda bastante feo la forma como se presenta el formulario.

118

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 5
Crear la clase python para las secciones (seccin), la misma va tener los campos:
Name
char
128
requerido.
start_date
date
duration
float
Seats
integer
Crear su vistas (form e tree) como tambin el menu y actin.

Class session dentro del archivo openacademy.py


class openacademy_session(osv.osv):
_name = 'openacademy.session'
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
}
openacademy_session()

Vistas de la session agregados en el archivo openacademy_view.xml


<!--Session-->
<record model="ir.ui.view" id="view_openacademy_session_tree">
<field name="name">view.openacademy.session.tree</field>
<field name="model">openacademy.session</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_openacademy_session_form">
<field name="name">sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<!--<field name="limit">100</field>-->
</record>
<menuitem id="menu_action_openacademy_session_form" name="Sessions" parent="menu_main_openacademy"
action="action_openacademy_session_form" />

Veamos los screenshots;


Seccin Tree:

119

8. RESOLUCIN DE LOS EJERCICIOS.

Podremos ver que en la pantalla de seccin no tenemos opciones de bsqueda por


ningn campo. Esto se debe por no haber puesto en la vista el elemento select=1 que
dice a openerp que el campo debe ser indexado y deber ser usado para las busquedas.

Seccin form:

120

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 6
Ya tenemos 2 tablas cursos y secciones pero todavia no tenemos ninguna relacin entre
las mismas, utilizando de los campos relacionales crearemos la relacin un curso puede
tener varias seccins. Creamos el campo 'course_id' en la clase session utilizando la relacion many2one.
Ademas vamos agregar tambin en la vista form de session este campo course_id

Editamos openacademy.py
class openacademy_session(osv.osv):
_name = 'openacademy.session'
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
}
openacademy_session()

En azul los cambio necesario.


Y agregamos en la vista del archivo openacademy_view.xml
<!--Session-->
<!--Define Vista lista-->
<record model="ir.ui.view" id="view_openacademy_session_tree">
<field name="name">view.openacademy.session.tree</field>
<field name="model">openacademy.session</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
</tree>
</field>
</record>
<!--Define Vista formulario-->
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
</form>
</field>
</record>

Aqui tenemos ya con el campo cursos. Vean que como el relacionamiento del campo es one2many del lado de seccin solamente tenemos 1 curso.

121

8. RESOLUCIN DE LOS EJERCICIOS.

122

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 7
Vamos ordenar la vista formulario de los cursos utilizando el notebooks para separar la
descripcin de los cursos y tener oportunidad de agregar nuevas pestaa con otros datos.

Para esto cambiamos nomas las vista de formulario del curso.


<!--Define Vista formulario-->
<record model="ir.ui.view" id="view_openacademy_course_form">
<field name="name">view.openacademy.course.form</field>
<field name="model">openacademy.course</field>
<field name="type">form</field> <!--Si aqu es de tipo form...-->
<field name="arch" type="xml">
<form string="Course"> <!--Aqu las etiquetas deben ser del mismo tipo-->
<field name="name" select="1" string="Otro String" colspan="4"/>
<notebook colspan="4">
<page string="Notes">
<newline/>
<separator string="Description" colspan="4"/>
<field name="description" select="1" nolabel="1" colspan="4"/>
</page>
</notebook>
</form>
</field>
</record>

Con esto tenemos ya la vista mas linda del formulario.

123

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 8
Crear la ultima clase attendee (participantes) que contendr 2 campos many2one que serian:
partner_id relacionado con la classe res.partner
session_id relacionado con la classe openacademy.session
class openacademy_attendee(osv.osv):
_name = 'openacademy.attendee'
_rec_name = 'partner.id'
_columns = {
'partner_id': fields.many2one('res.partner', 'Partner', required=True, ondelete='cascade'),
'session_id': fields.many2one('openacademy.session', 'Session', required=True, ondelete='cascade'),
}
openacademy_attendee()

Notemos que estamos utilizando el atributo _rec_name en la classe


openacademy_attendee ya que es obligatorio que la classe tenga siempre el campo
name que es si o si utilizado por el ORM para mostrar en las busquedas y sin el campo name tenemos que si o si especificar un nuevo campo que tendra esta funcion utilizando el atributo _rec_name.
Y las vistas correspondientes:
<!--Attendee-->
<record model="ir.ui.view" id="view_openacademy_attendee_tree">
<field name="name">view.openacademy.attendee.tree</field>
<field name="model">openacademy.attendee</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="attendee">
<field name="partner_id"/>
<field name="session_id"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_openacademy_attendee_form">
<field name="name">view.openacademy.attendee.form</field>
<field name="model">openacademy.attendee</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="attendee">
<field name="partner_id"/>
<field name="session_id"/>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_openacademy_attendee_form">
<field name="name">attendees</field>
<field name="res_model">openacademy.attendee</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<!--<field name="limit">100</field>-->
</record>
<menuitem id="menu_action_openacademy_attendee_form" name="attendees" parent="menu_main_openacademy"
action="action_openacademy_attendee_form" />

124

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 9
Como creamos mas una clase que esta relacionado con la clase session tenemos que
atualizar esta clase con el nuevo campo attendee y tambien la vista form de la clase session.
Abajo la clase session y en azul

class openacademy_session(osv.osv):
_name = 'openacademy.session'
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees')
}
openacademy_session()

Agreguemos tambien en la vista formulario el campo correspondiente.


<!--Define Vista formulario-->
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
<field name="attendee_ids">
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees">
<field name="partner_id"/>
</tree>
</field>
</form>
</field>
</record>

Siempre que un campo es del tipo one2many que por logica tendramos varios participantes en una seccin tendremos que en la vista form tener una vista tree para que
nos muestre un grid con los varios participantes. Como vemos abajo.

125

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 10
En el ejercicio 6 habiamos creado en la clase seccin el campo relacional curse_id pero no
creamos la contrapartida en la clase course o sea agreguemos el campo 'session_ids' del
tipo one2many que nos va dar la posibilidade de aceder a los respectivos datos de qualquier una de las clases.
Otro cambio que podremos hacer al la clase cursos es tambien agregar un responsable ao
curso que va usar el usuario del sistema como base para guardar quien agrego el curso y
va usar la clase ya existente de openerp res.user. El campo seria:
'responsible_id' del tipo many2one relacionado a la clase res.user.
Vamos agregar tambien estos campos en sus respectivas vistas siguiendo la imagen abajo.

Consejo: utilizar la tag group siempre que utiliza 2 tipos de vistas y/o notbook para que el
layout no tenga comportamientos inesperados.

class openacademy_course(osv.osv): # nombre de la clase para PYTHON


_name = 'openacademy.course' # nombre del objeto para OPENERP
#_table = 'otra_cosa'
_columns = {
# 'key':'value'
# 'nombre de los campos' : 'objeto de tipo de datos, su etiqueta y
# sus caractersticas de tamao, etc. en la base de datos'
'name' : fields.char('Name', size=128, required=True),
'description' : fields.text('Description'),
'responsible_id' : fields.many2one('res.users', 'Responsible'),
'session_ids': fields.one2many('openacademy.session', 'course_id', 'Sessions')
}
# Aqu se crea una instancia y sta al cargarse, crea la base de datos
# Despus debe agregarse el import al __ini__.py y despus se para el
# servidor, se actualiza el mdulo con
# python openerp-server.py -c openerp-server-ctp-openerp6.conf --update=openacademy
openacademy_course()
class openacademy_session(osv.osv):
_name = 'openacademy.session'
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
'instructor_id': fields.many2one('res.partner', 'Instructor')
}
openacademy_session()
<record model="ir.ui.view" id="view_openacademy_course_tree">
<!--Por convencin, las vistas comienzan con view + nombre del
objeto + tipo de vista, usando puntos como separador-->
<field name="name">view.openacademy.course.tree</field>
<!--El nombre del modelo lo definimos en la clase de python en el
archivo openacademy.py-->
<field name="model">openacademy.course</field>
<!--Si aqu es de tipo tree...-->
<field name="type">tree</field>
<field name="arch" type="xml">
<!--... las etiquetas deben ser del mismo tipo-->
<tree string="Course">
<!--nombre de cada uno de los campos de la base de datos
que deseo mostrar en la vista-->
<field name="name"/>
<field name="responsible_id"/>
</tree>
</field>
</record>

126

8. RESOLUCIN DE LOS EJERCICIOS.


<record model="ir.ui.view" id="view_openacademy_course_form">
<field name="name">view.openacademy.course.form</field>
<field name="model">openacademy.course</field>
<!--Si aqu es de tipo form...-->
<field name="type">form</field>
<field name="arch" type="xml">
<!--Aqu las etiquetas deben ser del mismo tipo-->
<form string="Course">
<group col="4" colspan="4">
<!--nombre de cada uno de los campos de la base de datos
que deseo mostrar en la vista-->
<field name="name" select="1" string="Otro String" colspan="4" />
</group>
<!--Aqu utiliza tabs para mostrar agrupando campos-->
<notebook colspan="4">
<!--Esta es una pgina de los tabs - el string es el ttulo del tab-->
<page string="General Info">
<field name="responsible_id"/>
</page>
<page string="Sessions">
<field name="session_ids" colspan="4" nolabel="1">
<form string="Session">
<field name="name"/>
<field name="instructor_id"/>
</form>
<tree string="Session">
<field name="name"/>
<field name="instructor_id"/>
</tree>
</field>
</page>
<page string="Notes">
<newline/>
<!--Esto es un separador, mediante texto-->
<separator string="Description" colspan="4"/>
<!--Los atributos permitidos/disponibles para los campos
se pueden consultar en el cdigo fuente-->
<field name="description" select="1" nolabel="1" colspan="4"/>
</page>
</notebook>
</form>
</field>
</record>

127

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 11
Como tenemos la clase attendee (participantes) creamos esta clase utilizando la clase partner para saber quienes son los alumnos pero tambien utilizamos partner para definir quien
es instructor el la clase seccion. En el futuro quiero que quando trate de seleccionar un ins tructor no me traiga en el sitado todos los partner y si solamento los que fueron cargados
como intructor, tendriamos 2 formas de hacer eso, creando una nueva clase instructor, o
creando un campo en partner que diga quando se esta cargando un instructor. Entonces
vamos utilizar la propria clase partner y agregar un campo llamado 'is_instructor' donde
sera un campo boleano si el partner creado es un instructor. Para hacerlo tenemos que utilizar herencias ya que si cambio el modulo original cuando se actualize el modulo stantart
perderiamos el cambio.En este Ejercicio utilizando el recurso de herencia tanto de la clase
como de la vista agreguemos el campo is_instructor tipo bolleano que va estar posicionado luego despues del campo Suppier de la vista partner.

Lo primero que tenemos que hacer ya que vamos a heredar la clase partner para
agregar un campo mas, tenemos que decir al identificador de nuestro modulo que vamos crear un nuevo archivo llamado partner.py para eso editamos el archivo
__init__.py y agregamos la declaracin del partner.py
__init__.py
#-*- coding: utf-8 -*# Aqu deben declararse los imports de TODOS los archivos py que contenga
# nuestro modulo, para que puedan ser compilados por openERP y as procesados.
# vean

que no es necesario declarar la extensin del archivo python

import openacademy
import partner

Como vamos a crear la herencia de la vista tambien vamos tener que crear el archivo partner_view.xml que va contener la vista heredada tenemos que agregar el mismo
tambien en el archivo __openerp__.py
__openerp__.py
{

# Este es el archivo responsable por la descripcin del modulo


# Los dados aqu presente sern mostrado en las opciones de importacin
# del modulo en OpenERP
"name": "Open Academy",
"version": "1.0",
# Dependencias del modulo
"depends": ["base"],
"author": "Fadel Damen Schreiner",
"category": "Academy",
# Aqu debe escribirse una breve descripcin de lo que hace el mdulo.
"description": """
Open Academy Modulo para mantenimiento de entrenamiento:
- Cursos
- Secciones
- Participantes (registro)
""",
# Aqu se declaran todos los xml que contienen la definicin de los menus,
# acciones y vistas que utilizan el mdulo.
'update_xml': ['openacademy_view.xml',
'partner_view.xml'],
'demo': [],
'installable': True,
'active': False,
'certificate': False,

128

8. RESOLUCIN DE LOS EJERCICIOS.


}

Vamos ahora realmente crear el archivo de herencia del partner agregando el campo is_instructor.
partner.py
# -*- coding: utf-8 -*##########################################
# OpenERP, modulo creado por: Felipe Chiu
#from carpeta import archivo
#(archivo.clase)
##########################################
from osv import osv
from osv import fields
# nombre de la clase para PYTHON:
class res_partner(osv.osv):
# mucho ojo: cuando se heredan modulos SIEMPRE asegurarse
# de escribir INHERIT, en vez de _name
_inherit = 'res.partner'
_columns = {
'is_instructor': fields.boolean('Instructor', help="Mrquelo si el partner es instructor de Open Academy"),
}
res_partner()

Si reparan el la declaracin de de la clase tenemos el class que es la definicin de


la clase que es res_partner que es el mismo nombre de una clase que ya existe, luego
usamos el atributo _inherit que dice de donde viene la herencia en nuestro caso de
res.partner, si dejamos asi el comportamiento de openerp es agregar al partner lo que
definamos en esta clase, o sea cambiar su funcionalidad en todos los modulos que lo
utilizen, pero tambin es posible por ejemplo agregar el atributo _name='otro_nombre'
que haria que openerp creara una nueva clase y dejaba la clase padre intacta, o sea asi
tenemos la clase res.partner y otro_nombre y debriamos crear un nuevo menu para la
clase otro_nombre y luego openerp crearia una nueva tabla para esta clase.
Resumiendo tenemos como cambiar el funcionamiento de una clase que ya exista o
aprovechar una clase que ya exista para crear una nueva agregando o retirando caractersticas.
En nuestro modulo vamos cambiar el comportamiento de la clase res_partner.
Y por fin vamos a crear la nueva vista.
partner_view.xml
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_res_partner_form_inh_openacademy_01">
<field name="name">view.res.partner.form.inh.openacademy.01</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="supplier" position="after">
<field name="is_instructor"/>
</field>
</field>
</record>
</data>
</openerp>

129

8. RESOLUCIN DE LOS EJERCICIOS.


Vemos que para agregar una campo nuevo en una vista simplesmente es igual que
definir un vista de un campo (field) de un campo ya existente en la vista anterior en
nuestro caso es supplier luego un atributo llamado position que define donde vamos a
poner los campos nuevos y adentro de este field definimos en nuestro caso que agregue el campo is_istructor

130

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 12
Agregar un dominio a la busqueda del campo instructor_id que solamente traiga los registros que en el partner este en verdadero el campo is_instructor y supplier tambin sea verdadero.
Tambin agreguemos en la vista formulario de la clase session que se pueda ver los ins tructores relacionados.
Es bastante simples agregar los filtros en las consultas de openerp como vemos abajo utilizamos el
atributo domain con las condiciones y siempre antes de las condiciones el operador relacional que en
este caso es and (&).
class openacademy_session(osv.osv):
_name = 'openacademy.session'
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
'instructor_id': fields.many2one('res.partner', 'Instructor', domain=['&',('is_instructor','=', True),
('supplier','=', True)])
}
openacademy_session()

Lluego agregamos en la vista de session para que posamos ver los intructores relacionados, como vemos abajo.
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
<field name="instructor_id"/>
<field name="attendee_ids" colspan="4">
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees" editable="top">
<field name="partner_id"/>
</tree>
</field>
</form>
</field>
</record>

131

8. RESOLUCIN DE LOS EJERCICIOS.


Debemos quedar con la vista de la siguiente forma:

132

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 13
Cambiemos el uso del dominio solamente el la vista del modulo.

Al aplicar estos cambios le funcionamiento de nuestro modulo quedara en lo mismo. Abajos solamente comentamos la definicin del campo y dejamos como instructivo el mecanismo de como se usa.
class openacademy_session(osv.osv):
_name = 'openacademy.session'
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
# domain puede estar aqu y afecta todo el mdulo, en la vista (xml) y afecta la vista solamente
#'instructor_id': fields.many2one('res.partner', 'Instructor', domain=['&',('is_instructor','=', True),
#('supplier','=', True)])
'instructor_id': fields.many2one('res.partner', 'Instructor')
}
openacademy_session()

Luego aplicamos en la vista solamente el dominio como mostrado abajo.


<!--Define Vista formulario-->
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
<!--Aqu se muestran subvistas dependiendo de donde
se acceda, ya sea el formulario la lista. Se
hizo porque apareca el campo de session cuando
ya estbamos en una nueva.
Hacer pruebas !!!!!-->
<!-- <field name="instructor_id"/> se coment para incluir el ejemplo de dominio para el xml-->
<field name="instructor_id" domain="['&', ('is_instructor', '=', True),
('supplier','=',True) ]"/>
<field name="attendee_ids" colspan="4">
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees" editable="top">
<field name="partner_id"/>
</tree>
</field>
</form>
</field>
</record>

133

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 14
Agreguemos en la clase partner heredada la posibilidad de ya agregar la seccin del curso
en el momento de su carga.
class res_partner(osv.osv):
# mucho ojo: cuando se heredan modulos SIEMPRE asegurarse
# de escribir INHERIT, en vez de _name
_inherit = 'res.partner'
_columns = {
'is_instructor': fields.boolean('Instructor', help="Mrquelo si el partner es instructor de Open Academy"),
'session_ids': fields.many2many('openacademy.session', 'res_partner_session_rel', 'partner_id',
'session_id', 'Sessions'),
}
res_partner()

Y tambin tenemos que cambiar la vista. Que como es heredada quedara como
abajo mostrado.
<record model="ir.ui.view" id="view_res_partner_form_inh_openacademy_01">
<field name="name">view.res.partner.form.inh.openacademy.01</field>
<!--Este es el modelo original-->
<field name="model">res.partner</field>
<!--Esta es el -->
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<!--Este es el control que antecede al control que deseo insertar.
Position puede ser after, before, replace e inside
(inside solo para insertar en controles page) -->
<field name="supplier" position="after">
<!--Este es el nico campo/control que se va a agregar
a la vista original-->
<field name="is_instructor"/>
</field>
<xpath expr="/form/notebook/page[@string='Notes']" position="after">
<page string="Sessions">
<field name="session_ids" nolabel="1" colspan="4"/>
</page>
</xpath>
</field>
</record>

134

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 15
Crear un campo calculado que muestre el porcentaje de cupos que tiene una seccin de
cursos, por ejemplo una seccin se define que tiene 10 cupos y que muestre el porcentaje
que tenemos completo el cupo.
Ejemplo:
Veamos que tenemos un barra de progreso en nuestra vista.

Para implementar esta funcionalidad tenemos que definir 1 o mas funciones en


nuestra clase session y un campo calculado. Para eso creamos lo siguientes codigo.
class openacademy_session(osv.osv): # nombre de la clase para PYTHON
_name = 'openacademy.session' # nombre del objeto para OPENERP
def _get_remaining_seats_percent(self,seats,attendee_list):
return seats and ((100.0 * (seats - len(attendee_list)))/ seats) or 0
def _remaining_seats_percent(self,cr,uid,ids,field,arg,context=None):
#count the percentage of remaining seats
result = {}
# browse = obtiene los objetos de la tabla para manejarlo como objeto
sessions = self.browse(cr,uid,ids,context=context)
for session in sessions :
result[session.id] = self._get_remaining_seats_percent(session.seats,
session.attendee_ids)
return result
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
# domain = filtro a aplicar a los datos de una tabla, proporcionando criterios.
# equivale a las clusulas where de una consulta SQL.
# sintaxis: domain=['operador_para_criterios', ('campo_de_bd', 'operador_logico', 'valor_buscado
boleano'), (otro criterio), (se repite las veces que sea necesario)]
# domain puede estar aqu y afecta a los datos que se muestran en todo el mdulo,
# puede declararse en la vista (xml) y afecta solamente donde se usa sta.
#'instructor_id': fields.many2one('res.partner', 'Instructor', domain=['&',('is_instructor','=', True),
#('supplier','=', True)])
'instructor_id': fields.many2one('res.partner', 'Instructor'),
# la funcin a llamar aqu, se debe declarar siempre arriba de la seccin columns
# esta funcin se ejecuta solamente cuando se abre la vista !
#'remaining_seats_percent': fields.function(_get_remaining_seats_percent,)
'remaining_seats_percent':fields.function(_remaining_seats_percent,
method=True,type='float',
string='Remaining seats'),
}
openacademy_session()

Vamos ahora explicar um poco el codigo fuente arriba, en primer lugar veremos la
declaracin del campo calculado que sigue:
'remaining_seats_percent':fields.function(_remaining_seats_percent,
method=True,type='float',
string='Remaining seats'),

Definimos un campo llamado remaining_seats_percent, que traduciendo es Percentual de lugares sobrantes. Que es un campo calculado en el primer parametro pasamos
la funcin python que va realizar el calculo, tambin pasamos el method como True
para decir que la llamada es un metodo, y no una funcin generica, que estaria a fuera
de la clase. Y luego definimos el tipo de resultado que va ser un float, e por ultimo el
nombre el campo.

135

8. RESOLUCIN DE LOS EJERCICIOS.


En esta funcin pasamos el metodo _remaining_seats_percent que vemos abajo y
comentamos linea a linea:
def _remaining_seats_percent(self,cr,uid,ids,field,arg,context=None):
#calcula el percentual de lugares sobrantes
result = {}
# browse = obtiene los objetos de la tabla para manejarlo como objeto
sessions = self.browse(cr,uid,ids,context=context)
for session in sessions :
result[session.id] = self._get_remaining_seats_percent(session.seats,
session.attendee_ids)
return result

Comentando las lineas en rojo, definimos una variables sessions y utilizamos un


metodo del ORM de openerp que es el browse que por definicin trae los datos de una
clase por completo como esta definidos como self, dice a python que va ejecutar el
metodo browse para la clase que estamos que seria session, como el browse tiene sus
parametros estandart, el va traer todos los registros de session y guardar en un diccionario Sessions. La ventaja del orm es que podremos acesar los valores de los campos .
En el codigo arriba passamos a otro metodo que calcule el porcentaje que es el
_get_remaining_seats_percent que es pasado como parametro session.seats, session.attendee_ids que son los dados de registro a registro de la tabla session el campo
seats y los id de participantes. Es un diccionario.
Utilizando el recurso de depuracin de codigo podriamos agregar algunos print en
el codigo para tratar de entender los datos manejados.
def _remaining_seats_percent(self,cr,uid,ids,field,arg,context=None):
#count the percentage of remaining seats
result = {}
# browse = obtiene los objetos de la tabla para manejarlo como objeto
sessions = self.browse(cr,uid,ids,context=context)
for session in sessions :
print 'Session.............: ', session
print 'session.descripcion.: ', session.name
print 'session.assentos....: ',session.seats
n=1
for attendee in session.attendee_ids :
print '
participantes ..: ', n, attendee.partner_id.name
n=n+1
result[session.id] = self._get_remaining_seats_percent(session.seats,
session.attendee_ids)
return result

Esto nos va imprimir en la consola (log) de openerp los datos asi:


Session.............:
session.descripcion.:
session.assentos....:
participantes ..:
participantes ..:

browse_record(openacademy.session, 1)
sesion 1
10
1 wagner martinez
2 Fadel Damen Schreiner

El metodo browse estiro los datos de la tabla sessin y tambien de la tabla detalle
de participantes (attendee) y lo hacemos un recorrido o obtenemos por ejemplo la sessin 1 con 10 cupos y ya agregado 2 participantes.

136

8. RESOLUCIN DE LOS EJERCICIOS.


Con estos datos vamos interpretar el segundo metodo:
def _get_remaining_seats_percent(self,seats,attendee_list):
return seats and ((100.0 * (seats - len(attendee_list)))/ seats) or 0

Y su llamada:
result[session.id] = self._get_remaining_seats_percent(session.seats,
session.attendee_ids)

Siguiendo el razonamiento de los datos anteriores esta llamada esta enviando el


session.seats que en nuestro caso es 10, y un array de participantes (attendee_ids) que
son 2 el metodo recibe y va retornar 1 valor pero notamos que en el return de segundo
metodo tenemos una expresin compleja return seats and (calculo) or 0 en python hacemos que se verifique la expresion seats si tiene valor diferente de 0 es verdadero ya
si tiene el valor 0 es falso y ya no hace el calculo y retorna el 0 por el or. Esto hace
que no nos de error al calcular valores con 0 para no dar el error de divid by zero. Si el
valor de seats tiene valor retorna verdadero y lluego ejecuta la expressin que es el
calculo y retorna este valor. Usando los valores de ejemplo 100.0 * (10 2)/10 que
nos da 80, reparen que usamos 100.0 que en python es la forma de forzar que el resultado sea float y no entero.
Ahora nos queda la vista para mostrar en el tree y en el form el progress bar.
<record model="ir.ui.view" id="view_openacademy_session_tree">
<field name="name">view.openacademy.session.tree</field>
<field name="model">openacademy.session</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
<field name="instructor_id"/>
<field name="remaining_seats_percent" widget="progressbar"/>
</tree>
</field>
</record>
<!--Define Vista formulario-->
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
<field name="instructor_id" domain="['&', ('is_instructor', '=', True),
('supplier','=',True) ]"/>
<field name="remaining_seats_percent" widget="progressbar"/>
<field name="attendee_ids" colspan="4">
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees" editable="top">
<field name="partner_id"/>
</tree>
</field>
</form>
</field>
</record>

Notamos que lo unico diferente que hacemos es el uso de un nuevo widget llamado
progressbar.
137

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 16

Crear un evento para el campo seats que valide si el valor de seats es negativo presenta una mensaje y vuelve los valores que tenia grabado en la base de datos.
class openacademy_session(osv.osv): # nombre de la clase para PYTHON
_name = 'openacademy.session' # nombre del objeto para OPENERP
def onchange_remaining_seats(self, cr, uid, ids, seats, attendee_ids):
remaining_seats_percent = self._get_remaining_seats_percent(seats, attendee_ids)
res = {}
res.update({
'value': {
'remaining_seats_percent': remaining_seats_percent,#campo del modelo
}
})
if seats < 0:
#if remaining_seats_percent < 0:
res.update({
'warning': {
'title': 'Warning',
'message': 'You cannot have negative seats',
}
})
session_obj = self.pool.get('openacademy.session')
session = session_obj.browse(cr, uid, ids, context=None)[0]
res['value']['seats'] = session.seats
res['value']['remaining_seats_percent'] = session.remaining_seats_percent
return res
def _get_remaining_seats_percent(self,seats,attendee_list):
return seats and ((100.0 * (seats - len(attendee_list)))/ seats) or 0
def _remaining_seats_percent(self,cr,uid,ids,field,arg,context=None):
#count the percentage of remaining seats
result = {}
# browse = obtiene los objetos de la tabla para manejarlo como objeto
sessions = self.browse(cr,uid,ids,context=context)
for session in sessions :
result[session.id] = self._get_remaining_seats_percent(session.seats,
session.attendee_ids)
return result
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
# domain = filtro a aplicar a los datos de una tabla, proporcionando criterios.
# equivale a las clusulas where de una consulta SQL.
# sintaxis: domain=['operador_para_criterios', ('campo_de_bd', 'operador_logico', 'valor_buscado
boleano'), (otro criterio), (se repite las veces que sea necesario)]
# domain puede estar aqu y afecta a los datos que se muestran en todo el mdulo,
# puede declararse en la vista (xml) y afecta solamente donde se usa sta.
#'instructor_id': fields.many2one('res.partner', 'Instructor', domain=['&',('is_instructor','=', True),
#('supplier','=', True)])
'instructor_id': fields.many2one('res.partner', 'Instructor'),
# la funcin a llamar aqu, se debe declarar siempre arriba de la seccin columns
# esta funcin se ejecuta solamente cuando se abre la vista !
#'remaining_seats_percent': fields.function(_get_remaining_seats_percent,)
'remaining_seats_percent':fields.function(_remaining_seats_percent,
method=True,type='float',
string='Remaining seats'),
}
openacademy_session()

Es un mtodo que recibe 2 parametros seats y attendee donde attendee es un diccionario como en el metodo de ejercicio anterior que contiene los varios participantes.
El metodo llama a otro metodo que creamos en ejercicio pasado para calcular el
porcentaje enviando tambin los parametros seats y attendee y recibe el porcentaje correspondiente luego creamos un diccionario en blanco y luego actualizamos su valor
con un metodo update para fins didaticos muestra que en python las variables tienen

138

8. RESOLUCIN DE LOS EJERCICIOS.


sus metodos ai verificamos si el seats digitado es menor que 0 si verdadero actualizamos el diccionario con un diccionario nombrado warning que pasamos los elementos
title y mensaje, con esto ya garantizamos que un mensaje se va presentar al usuario, y
tambin agregamos al mismo diccionario un elemento update y le pasamos valores a 2
campos seats y remaining_seats_percent. Pero para atribuir el valor a estos 2 campos
utilizamos 2 metodo del ORM de OpenERP. Estamos utilizando el metodo pool.get a
fines didaticos porque podriamos utilizar el self de la clase ya que es el misma clase
que estamos tratando los datos, pero el metodo pool.get es usado para buscar dados de
otras clases que incluso pueden se de otros modulos en el sistema. Usamos el metodo
browse vi vemos que seleccionamos [0] al final del mismo que es uns sentencia python que va utilizar el primer registro solamente que en nuestro caso sera el registro
corrente al momento de la llamada del atributo on_change.
Solo tenemos ahora que cambiar la vista formulario de la classe session para que
llame el metodo arriba al salir de campo.
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats" on_change="onchange_remaining_seats(seats, attendee_ids)"/>
<field name="course_id"/>
<field name="remaining_seats_percent" widget="progressbar"/>
<!--Aqu se muestran subvistas dependiendo de donde
se acceda, ya sea el formulario la lista. Se
hizo porque apareca el campo de session cuando
ya estbamos en una nueva.
Hacer pruebas !!!!!-->
<field name="instructor_id" domain="['&', ('is_instructor', '=', True),
('supplier','=',True) ]"/>
<field name="attendee_ids" colspan="4" on_change="onchange_remaining_seats(seats,
attendee_ids)">
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees" editable="top">
<field name="partner_id"/>
</tree>
</field>
</form>
</field>
</record>

En la vista disparamos el on_change en 2 oportunidades al cambiar el campo seats


y al agregar o remover participantes.

139

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 17

Crear 2 constraints de los tipos _sql_constraints y _constraints, la primera validando


que el titulo de los cursos sean unicos (no permita que sean iguales) y la segunda
verificando si el titulo y descripcin son iguales, al dar guardar en la tabla de cursos.
Para utilizar el recurso de contraints en OpenERP solamente hacemos cambios en
la clase que vamos aplicar las verificaciones que en nuestro ejemplo es la clase cursos.
Abajo tenemos los codigos utilizados.
class openacademy_course(osv.osv): # nombre de la clase para PYTHON
_name = 'openacademy.course' # nombre del objeto para OPENERP
#_table = 'otra_cosa'
_columns = {
# sintaxis: 'key':'value'
# key='nombre de los campos' : value='objeto de tipo de datos, su etiqueta y
# sus caractersticas de tamao, etc. en la base de datos'
'name' : fields.char('Name', size=128, required=True),
'description' : fields.text('Description'),
# many2one debemos asignar siempre el modelo a usar y el nombre, a mostrar
# opcionalmente atributos especiales.
# ojo: si no se proporciona la forma de borrar (ondelete) el default es
# set_null. Otras opciones son cascade y ??? !
'responsible_id' : fields.many2one('res.users', 'Responsible'),
# one2many debemos siempre referenciar el modelo, el campo a referenciar y el
# ttulo a usar y opcionalmente atributos especiales.
# Notese que el nombre del campo termina en plural, esto es por convencin.
'session_ids': fields.one2many('openacademy.session', 'course_id', 'Sessions')
}
#Check whether the course title and the course description are not the same
def _check_description(self,cr,uid,ids,context=None):
courses = self.browse(cr,uid,ids,context=context)
check = True
for course in courses:
#print course.description==course.name
if course.name==course.description:
return False
return check
# sintaxis:
# _contraints=[(nombre_de_la_funcion_a_usar, mensaje_de_advertencia_para_el_usuario, campos_a_monitorear)]
# esta restriccin es a nivel de aplicacin solamente.
_constraints = [(_check_description, 'Please use a different description', ['name','description'])]
# sintaxis:
# _sql_contraints=[(nombre_a_usar, tipo_de_restricion(nombre_campo) - opciones de postgressql(unique,
foreignkey y check), mensaje_de_advertencia_para_el_usuario)]
# este es una restriccin que se aplica permanentemente (mediante un alter), desde openERP a la base de datos.
# siempre deben declararse desde aqu y no directamente en la base de datos
# para que openERP pueda interceptar los errores y presentar el mensaje al usuario.
_sql_constraints = [('unique_name', 'unique(name)', 'Course Title must be unique'),]
# Aqu se crea una instancia y sta -al cargarse-, crea la base de datos
# Despus debe agregarse el import al __ini__.py, despus se para el
# servidor, se actualiza el mdulo con
# python openerp-server.py -c openerp-server-ctp-openerp6.conf --update=openacademy
# el string del update es la carpeta donde se guardan los archivos py y xml del mdulo.
openacademy_course()

Con el codigo bastante comentado queda bastado creo que queada bien explicado
sus funcionamiento.
El que vale comentario son para el _contraints que debemos crear un metodo que
retorne solamente verdadero o falso y es pasado como primer parametro, leugo pasamos el mensaje que sera presentado al usuario y como ultimo parametro pasamos a
los campos que openerp va quedar monitoreando para dispara la constraints.

140

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 18

Como hicimos los contraints que verifican que no se pueda duplicar un nombre de
curso si tratamos de utilizar el recurso de openerp de duplicar un registro nos va generar un error acionando la constraints para eso tenemos que re-implementar el mtodo copy estandart para que por ejemplo agregue al nombre nuevo el termino (copia).
Agregamos le metodo abajo en la clase curso siempre antes de la definicin de los
campos.
Estamos utilizando el copy original de openERP y estamos cambiando su comportamiento
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
course_obj = self.pool.get('openacademy.course')
course = course_obj.browse(cr, uid, id, context=context)
new_name = course.name + ' (copy)'
list = course_obj.search(cr, uid, [('name','like',new_name)], context=context)
if len(list)>0:
new_name='%s (%s)' % (new_name,len(list)+1)
default.update({'name': new_name}):
# aqu instanciamos el copy original y le pasamos los parmetros de nuestra version
new_id = super(openacademy_course, self).copy(cr, uid, id, default, context=context)
return new_id

Analisando el codigo inicialmente como vamos a modificar el parametro default si


el no fue tocado definimos un diccionario em blanco. Luego ejecutamos un bfowse
del registro actual y le cargamos al metodo a la variable course luego acesamos el
elemento name del mismo y agregamos la string (copy), luego usamos el metodo search para buscar si existen registro ya con esta descripcin ya que podriamos clicar varias vezes en duplicar si lo encuentra le agrega mas vezes la sentenza (copy) + un numero de registros encontrados +1. luego con la tentencia de new_name creada agregamos la misma al diccionario default con el metodo update y llamamos la clase madre de copy que ira pasar en el parametro default.
O sea todo esto esta es echo para que sea creado un registro nuevo con una descipcin diferente para que no caiga el el contraints.

141

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 19

Crear en la clase session un campo nuevo llamado active del tipo boolean el mismo
es un campo de nombre reservado por openerp con la funcin automtica de definir
la visibilidad del registro en las consultas.
Vamos tambin atribuir valores por defecto en el campo start_date y cambiar su tipo
de campo de date para datetime. Para esto tenemos que hacer un import en el archivo python de un paquete llamado time que possee funciones de python para tratar
campos del tipo hora. Agreguemos tambien el valor por defecto al campo active
para true.
import time
class openacademy_session(osv.osv): # nombre de la clase para PYTHON
_name = 'openacademy.session' # nombre del objeto para OPENERP
def onchange_remaining_seats(self, cr, uid, ids, seats, attendee_ids):
# mandamos calcular el porcentaje remanente de asientos, llamando a la funcin ya existente
remaining_seats_percent = self._get_remaining_seats_percent(seats, attendee_ids)
# openERP espera que las funciones onchange devuelvan un diccionario como valor.
res = {}
# siempre hay que devolver un valor "value", ya que es la palabra reservada para regresar valores.
res.update({
'value': {
'remaining_seats_percent': remaining_seats_percent,#campo del modelo
}
})
if seats < 0:
#if remaining_seats_percent < 0:
# sin embargo se pueden devolver mensajes de advertencia al usuario,
# mediante la palabra reservada warning.
res.update({
'warning': {
'title': 'Warning',
'message': 'You cannot have negative seats',
}
})
# aqu estamos recuperando de la base de datos el valor original guardado
# de la cantidad de asientos definidos al curso.
session_obj = self.pool.get('openacademy.session')
session = session_obj.browse(cr, uid, ids, context=None)[0]
# y los asignamos nuevamente al campo en el formulario.
res['value']['seats'] = session.seats
# de la misma manera se devuelve el valor original del porcentaje.
res['value']['remaining_seats_percent'] = session.remaining_seats_percent
return res
def _get_remaining_seats_percent(self, seats, attendee_list):
return seats and ((100.0 * (seats - len(attendee_list)))/ seats) or 0
def _remaining_seats_percent(self, cr, uid, ids, field, arg, context=None):
#count the percentage of remaining seats
result = {}
# Quitar comentarios a estos campos de print para que muestre en la consola
# los valores que est pasando openERP a las funciones !
#print "uid",uid
#print "ids",ids
#print "field",field
# browse = obtiene los objetos de la tabla para manejarlo como objeto
# todas las funciones donde aparecen estos argmentos los pasa openERP en
# automtico cr= cursor, uid=userID, ids=dependiendo de la vista, regresa el id de
# del registro donde este posicionado si es una lista devuelve todos los ids de
# esa vista, context=equivale a **params (pasar todos los parmetros extras que queramos)
sessions = self.browse(cr, uid, ids, context=context)
for session in sessions :
result[session.id] = self._get_remaining_seats_percent(session.seats, session.attendee_ids)
return result
_columns = {
'name': fields.char('Name', size=128, required=True),
#'start_date': fields.date('Start Date'),
'start_date': fields.datetime('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
# domain = filtro a aplicar a los datos de una tabla, proporcionando criterios.
# equivale a las clusulas where de una consulta SQL.
# sintaxis: domain=['operador_para_criterios', ('campo_de_bd', 'operador_logico', 'valor_buscado
# boleano'), (otro criterio), (se repite las veces que sea necesario)]
# domain puede estar aqu y afecta a los datos que se muestran en todo el mdulo,
# puede declararse en la vista (xml) y afecta solamente donde se usa sta.

142

8. RESOLUCIN DE LOS EJERCICIOS.

#'instructor_id': fields.many2one('res.partner', 'Instructor', domain=['&',('is_instructor','=', True),


#('supplier','=', True)])
'instructor_id': fields.many2one('res.partner', 'Instructor'),
# la funcin a llamar aqu, se debe declarar siempre arriba de la seccin columns
# esta funcin se ejecuta solamente cuando se abre la vista !
#'remaining_seats_percent': fields.function(_get_remaining_seats_percent,)
'remaining_seats_percent':fields.function(_remaining_seats_percent,
method=True,type='float',
string='Remaining seats'),
'active': fields.boolean('Active', help="Un-Check this field for hidden record."),

#def _get_now_time(self, cr, uid, context={}):


#
return time.strftime('%Y-%m-%d %H:%M:%S')
# asigna los valores por default a los campos de la clase
_defaults = {
'active': True,
#'start_date': _get_now_time,
#'start_date': time.strftime('%Y-%m-%d %H:%M:%S'),
#'start_date': lambda self, cr, uid, context={}: time.strftime('%Y-%m-%d %H:%M:%S'),
# lambda es una funcin annima. Se ejecuta como una funcin normal pero solo para procesar
# una sola lnea de cdigo. Se le pueden pasar parametros decirle que no ser as, escribiendo
# *a.
'start_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
}
openacademy_session()

As tambin agreguemos los datos en la vista.


<!--Define Vista lista-->
<record model="ir.ui.view" id="view_openacademy_session_tree">
<field name="name">view.openacademy.session.tree</field>
<field name="model">openacademy.session</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
<field name="remaining_seats_percent" widget="progressbar"/>
<field name="active"/>
<field name="instructor_id"/>
</tree>
</field>
</record>
<!--Define Vista formulario-->
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats" on_change="onchange_remaining_seats(seats, attendee_ids)"/>
<field name="course_id"/>
<field name="remaining_seats_percent" widget="progressbar"/>
<field name="active"/>
<field name="instructor_id" domain="['&amp;', ('is_instructor', '=', True),
('supplier','=',True) ]"/>
<field name="attendee_ids" colspan="4" on_change="onchange_remaining_seats(seats,
attendee_ids)">
<!--<field name="attendee_ids" colspan="4">-->
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees" editable="top">
<field name="partner_id"/>
</tree>
</field>
</form>
</field>
</record>

143

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 20

En la vista tree de la clase session, hagamos que cuando la duracin sea menor que
5 el color de la linea sea #00ff00 y rojo si la duracin sea mayor de 15.
En este ejercicio lo que mas tenemos que cuidar es la utilizacin de los simbolos
especiales > y < en el atributo colors. Siguiendo la tabla de caracteres especiales para
la web.
Siendo as nuestra vista quedaria como mostrado abajo:
<!--Define Vista lista-->
<record model="ir.ui.view" id="view_openacademy_session_tree">
<field name="name">view.openacademy.session.tree</field>
<field name="model">openacademy.session</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="session" colors='#00ff00:duration&lt;5;red:duration>15'>
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
<field name="remaining_seats_percent" widget="progressbar"/>
<field name="active"/>
<field name="instructor_id"/>
</tree>
</field>
</record>

Vean que utilizamos la sintaxis:


colors=<color>:<campo><operador_relacional><valor>; <otrocolor> ...
Solo que en lugar de < utilizamos &lt; que es la notacin en html y dejamos el >
como carcter normal y funciona sin problemas.

144

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 21

Crear una vista nueva tipo calendario para la clase session usando los parametros
abajo:
date_start
campo start_date
date_delay
campo duration
day_length
'1'
color
campo instructor_id
Campo a mostrar
name
Una Simples implementacin ya que esta bien documentado utilizamos pocos parametros donde:
data_start es el campo que va utilizar para definir el inicio de elvento
date_delay o date_stop son tiempo de duracion en horas o date_stop cuando definimos una fecha final.
day_length cantidad de horas de un dia de trabajo estandart 8 horas.
Color se define el campo que va cambiar los colores en representacin en el calendario normalmente es un campo relacional.
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats" on_change="onchange_remaining_seats(seats, attendee_ids)"/>
<field name="course_id"/>
<field name="remaining_seats_percent" widget="progressbar"/>
<field name="active"/>
<!--Aqu se muestran subvistas dependiendo de donde
se acceda, ya sea el formulario la lista. Se
hizo porque apareca el campo de session cuando
ya estbamos en una nueva.
Hacer pruebas !!!!!-->
<field name="instructor_id" domain="['&amp;', ('is_instructor', '=', True),
('supplier','=',True) ]"/>
<field name="attendee_ids" colspan="4" on_change="onchange_remaining_seats(seats,
attendee_ids)">
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees" editable="top">
<field name="partner_id"/>
</tree>
</field>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_openacademy_session_calendar">
<field name="name">view.openacademy.session.calendar</field>
<field name="model">openacademy.session</field>
<field name="type">calendar</field>
<field name="arch" type="xml">
<calendar string="Session Calendar"
date_start="start_date"
date_delay="duration"
day_length="1"
color="instructor_id">
<field name="name"/>
</calendar>
</field>
</record>

145

8. RESOLUCIN DE LOS EJERCICIOS.


Y luego tenemos que cambiar el actin para que reconosca que tenemos una nueva
vista.
<!--Acciones-->
<record model="ir.actions.act_window" id="action_openacademy_session_form">
<field name="name">sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
<!--<field name="limit">100</field>-->
</record>

Veamos que con solamente agregando en el action en view model la vista calendarios y como no tenemos otra vista correspondiente el ya reconoce la vista declarada
anteriormente.

146

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 22

Agregar en la clase cursos un campo calculado que sumen los participantes de todas
la secciones el campo sera llamado 'attendee_count'.
Tambin agregar el campo funcion 'attendee_count' en la clase session que contenga el total de participantes de la session.
En modo general tenemos que cambiar la clase cursos y agregar el nuevo campo y
el mtodo que va ejecutar al asesar este campo.
class openacademy_course(osv.osv): # nombre de la clase para PYTHON
_name = 'openacademy.course' # nombre del objeto para OPENERP
def _get_attendee_count(self, cr, uid, ids, field, arg, context=None):
res = {}
session_obj = self.pool.get('openacademy.session')
course_obj = self.pool.get('openacademy.course')
courses = course_obj.browse(cr, uid, ids, context=context)
for course in courses:
attendee_cont = 0
for session in course.session_ids:
attendee_cont += len( session.attendee_ids )
res[ course.id ] = attendee_cont
return res
_columns = {
'name' : fields.char('Name', size=128, required=True),
'description' : fields.text('Description'),
'responsible_id' : fields.many2one('res.users', 'Responsible'),
'session_ids': fields.one2many('openacademy.session', 'course_id', 'Sessions'),
'attendee_count': fields.function(_get_attendee_count, type='integer', string='Attendee Count', method=True),
}
def _check_description(self,cr,uid,ids,context=None):
courses = self.browse(cr,uid,ids,context=context)
check = True
for course in courses:
#print course.description==course.name
if course.name==course.description:
return False
return check
_constraints = [(_check_description, 'Please use a different description', ['name','description'])]
_sql_constraints = [('unique_name', 'unique(name)', 'Course Title must be unique'),]
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
course_obj = self.pool.get('openacademy.course')
course = course_obj.browse(cr, uid, id, context=context)
new_name = course.name + ' (copy)'
list = course_obj.search(cr, uid, [('name','like',new_name)], context=context)
if len(list)>0:
new_name='%s (%s)' % (new_name,len(list)+1)
default.update({'name': new_name})
new_id = super(openacademy_course, self).copy(cr, uid, id, default, context=context)
return new_id
openacademy_course()

147

8. RESOLUCIN DE LOS EJERCICIOS.


Vamos analisar el codigo del metodo _get_attende_count
def _get_attendee_count(self, cr, uid, ids, field, arg, context=None):
res = {}
session_obj = self.pool.get('openacademy.session')
course_obj = self.pool.get('openacademy.course')
courses = course_obj.browse(cr, uid, ids, context=context)
for course in courses:
attendee_cont = 0
for session in course.session_ids:
attendee_cont += len( session.attendee_ids )
res[ course.id ] = attendee_cont
return res

res={}
inicializamos la variable res con un diccionario vazio.
Session_obj = self.pool.get('openacademy.session')
levanta en la variable session_obj los atributos de la clase openacademy.session
course_obj = self.pool.get('openacademy.course')
Lo mismo a lo anterior pero sobre la clase openacademy.course.
Courses = courses_obj.browse(cr,uid,ids,context=context)
trae al objeto courses un diccionario con los registros de cursos.
For course in courses:
por cada registro en el diccionario courses atribuye al objeto curses
attendee_cont=0
inicializa la variabel con 0
for session in course.session_ids:
para cada registro en el diccionario course.session_ids armazena en la variable session
sus valores.
attendee_cont += len( session.attendee_ids )
lee el contenido de session.attendee_ids y cuenta con len cuantos objetos tiene asignado o sea cuantos participantes id posee el objeto session.attendee_ids y suma a la variables attendee_cont como es un bucle va leer de todos las sessiones de los cursos.
Sumando todos los participantes.
res[ course.id ] = attendee_cont
es el retorno del metodo que nos trae el course.id referido y el valor sumado en attendee_count

148

8. RESOLUCIN DE LOS EJERCICIOS.


hagamos la implementacin tambin del campo attendee_count tambin en la clase
session.
class openacademy_session(osv.osv): # nombre de la clase para PYTHON
_name = 'openacademy.session' # nombre del objeto para OPENERP
def onchange_remaining_seats(self, cr, uid, ids, seats, attendee_ids):
remaining_seats_percent = self._get_remaining_seats_percent(seats, attendee_ids)
res = {}
res.update({
'value': {
'remaining_seats_percent': remaining_seats_percent,#campo del modelo
}
})
if seats < 0:
res.update({
'warning': {
'title': 'Warning',
'message': 'You cannot have negative seats',
}
})
session_obj = self.pool.get('openacademy.session')
session = session_obj.browse(cr, uid, ids, context=None)[0]
res['value']['seats'] = session.seats
res['value']['remaining_seats_percent'] = session.remaining_seats_percent
return res
def _get_remaining_seats_percent(self, seats, attendee_list):
return seats and ((100.0 * (seats - len(attendee_list)))/ seats) or 0
def _remaining_seats_percent(self, cr, uid, ids, field, arg, context=None):
result = {}
return result
def _get_attendee_count(self, cr, uid, ids, field, arg, context=None):
res = {}
session_obj = self.pool.get('openacademy.session')
sessions = session_obj.browse(cr, uid, ids, context=context)
for session in sessions:
res[session.id] = len( session.attendee_ids )
return res
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
'instructor_id': fields.many2one('res.partner', 'Instructor'),
'remaining_seats_percent':fields.function(_remaining_seats_percent,
method=True,type='float',
string='Remaining seats'),
'attendee_count': fields.function(_get_attendee_count, type='integer', string='Attendee Count',
method=True),
'active': fields.boolean('Active', help="Un-Check this field for hidden record."),
}
_defaults = {
'active': True,
'start_date': lambda *a: time.strftime('%Y-%m-%d'),
}
openacademy_session()

Lo mas importante es tambin el metodo _get_attende_count que es basicamente la


misma implementacin solo que solamente recorremos los registros de la session
abierta (registro actual)

149

8. RESOLUCIN DE LOS EJERCICIOS.


En continuacin vamos presentar estes campos nuevos en nuestras vistas:
Vista course_form:
<!--Define Vista formulario-->
<record model="ir.ui.view" id="view_openacademy_course_form">
<field name="name">view.openacademy.course.form</field>
<field name="model">openacademy.course</field>
<!--Si aqu es de tipo form...-->
<field name="type">form</field>
<field name="arch" type="xml">
<!--Aqu las etiquetas deben ser del mismo tipo-->
<form string="Course">
<!--nombre de cada uno de los campos de la base de datos
que deseo mostrar en la vista-->
<field name="name" select="1" string="Otro String"/>
<!--Aqu utiliza tabs para mostrar agrupando campos-->
<notebook>
<!--Esta es una pgina de los tabs - el string es el ttulo del tab-->
<page string="General Info">
<field name="responsible_id"/>
<field name="attendee_count"/>
</page>
<page string="Sessions">
<field name="session_ids" colspan="4" nolabel="1">
<form string="Session">
<field name="name"/>
<field name="instructor_id"/>
</form>
<tree string="Session">
<field name="name"/>
<field name="instructor_id"/>
</tree>
</field>
</page>
<page string="Notes">
<newline/>
<!--Esto es un separador, mediante texto-->
<separator string="Description" colspan="4"/>
<!--Los atributos permitidos/disponibles para los campos
se pueden consultar en la pgina 11 del manual del technical-->
<field name="description" select="1" nolabel="1" colspan="4"/>
</page>
</notebook>
</form>
</field>
</record>

Vista session_form:

<!--Define Vista formulario-->


<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats" on_change="onchange_remaining_seats(seats, attendee_ids)"/>
<field name="attendee_count"/>
<field name="course_id"/>
<field name="remaining_seats_percent" widget="progressbar"/>
<field name="active"/>
<!--Aqu se muestran subvistas dependiendo de donde
se acceda, ya sea el formulario la lista. Se
hizo porque apareca el campo de session cuando
ya estbamos en una nueva.
Hacer pruebas !!!!!-->
<!-- <field name="instructor_id"/> se coment para incluir el ejemplo de dominio para el xml
-->

<field name="instructor_id" domain="['&amp;', ('is_instructor', '=', True),


('supplier','=',True) ]"/>
<field name="attendee_ids" colspan="4" on_change="onchange_remaining_seats(seats,
attendee_ids)">
<!--<field name="attendee_ids" colspan="4">-->
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees" editable="top">
<field name="partner_id"/>
</tree>
</field>
</form>
</field>
</record>

150

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 23

Definir una vista tipo gantt para la clase sessin con los mismos datos de la vista
Calendar, cambiando el attributo color para que utilize course_id
Cremos mas una vista del ahora del tipo gantt como sigue abajo:
<!--Define Vista gantt-->
<record model="ir.ui.view" id="view_openacademy_session_gantt">
<field name="name">view.openacademy.session.gantt</field>
<field name="model">openacademy.session</field>
<field name="type">gantt</field>
<field name="arch" type="xml">
<gantt string="Session Gantt"
date_start="start_date"
date_delay="duration"
day_length="1"
color="course_id">
<level object="res.partner" link="instructor_id">
<field name="name"/>
</level>
</gantt>
</field>
</record>

Luego tenemos que agregar en el action su declaracin.


<!--Acciones-->
<record model="ir.actions.act_window" id="action_openacademy_session_form">
<field name="name">sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,gantt</field>
<!--<field name="limit">100</field>-->
</record>

151

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 24

Definir una vista graphs, tipo bar en la session, que contenga como campo X el
course_id y como eje Y el campo attendee_count sumando los mismos
Bastante sensillo ya que definimos en la classe session un campo calculado attendee_count que nos muestra el total de participantes por session.
<!--Define Vista graph-->
<record model="ir.ui.view" id="view_openacademy_session_graph">
<field name="name">view.openacademy.session.graph</field>
<field name="model">openacademy.session</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph string="Participations by Courses" type="bar">
<field name="course_id"/>
<field name="attendee_count" operator="+"/>
</graph>
</field>
</record>

Como definimos que el eje X es el curse y que el eje Y va ser attendee_count y


como podremos tener varios sessiones por cada curso como utilizamos el operator '+'
el va sumar automaticamente las varios participamentes por sessiones y sumar en el
grafico su total por el curso.
Luego solamente tenemos que declarar en la vista.
<record model="ir.actions.act_window" id="action_openacademy_session_form">
<field name="name">sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,gantt,graph</field>
<!--<field name="limit">100</field>-->
</record>

Es importante salientar que todo el trabajo duro en las vistas debe siempre ser tratados, calculados y guardados, por los campos calculados (tipo funcin) en el codigo de
la clase de los modulos.

152

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 25

Crear una vista de busqueda (search) para la clase cursos, que tenga un filtro mis
cursos, que filtre atravs del dominio "[('responsible_id','=',uid)]" que el icono utilizado sea "terp-partner". Esto nos resume que va listar en el grid solamente los cursos que el responsable sea el usuario que registro los cursos y quedo como responsable. Y que tambin que este filtro venga como estandarte seleccionado.
En primero creamos la vista Search.
<!--Define Vista Search-->
<record model="ir.ui.view" id="view_openacademy_course_search">
<field name="name">view.openacademy.course.search</field>
<field name="model">openacademy.course</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Course Search">
<filter string="My Courses" icon="terp-partner"
name="my_courses"
domain="[('responsible_id','=',uid)]"
help="My own ideas"/>
<field name="name"/>
</search>
</field>
</record>

La definicin de la vista Search es basicamente igual a la vista form o tree, solamente tenemos un nuevo atributo search que se le pasa el string con el nombre del comando icon con el icono, name con el nombre para referencia el el context, luego el
domain que es el filtro propiamente dicho del botn. Leugo de definir la vista tenemos
que pasar a la action que la misma sera utilizada. Cambiando el action que ya lo tenamos.
<!--Acciones-->
<record model="ir.actions.act_window" id="action_openacademy_course_form">
<field name="name">Courses</field>
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_openacademy_course_search"/>
<field name="context">{'search_default_my_courses': 1}</field>
<!--<field name="limit">100</field>-->
</record>

En azul tenemos la llamada de search pasando el atributo search_view_id el ref con


el nombre de la vista search a ser utilizada. Luego mandamos en el context que el
search default que va estar seleccionado sea el my_courses. Que esta en la vista search
en rojo.

153

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 26

Crear una vista de busqueda (search) para la clase session. Es recomendable utilizar
la vista search para definir los campos de busqueda que tambien se puede utilizar el
atributo select='1' en la vista tree.
En la versin anterior de openerp las vistas Tree tenan las busquedas definidas por
el atributo select=1 definidos en los fields, que tambin tiene el objetivo de mostrar
al orm que debe crear un indice para estos campos. Con la legada de las vistas tipo
Search por conversin debriamos utilizar esta vista para definir estos campos de busqueda ademas de darnos la posibilidad de crear los botones de dominios.
Abajo definimos las vista tipo search para las sessiones.
<!--Define Vista search-->
<record model="ir.ui.view" id="view_openacademy_session_search">
<field name="name">view.openacademy.session.search</field>
<field name="model">openacademy.session</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Session Search">
<field name="name"/>
<field name="instructor_id"/>
</search>
</field>
</record>

Tambin actualizamos el action para que tenga en cuenta la nueva vista.


<!--Acciones-->
<record model="ir.actions.act_window" id="action_openacademy_session_form">
<field name="name">sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,gantt,graph</field>
<field name="search_view_id" ref="view_openacademy_session_search"/>
<!--<field name="limit">100</field>-->
</record>

154

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 27

Editar la vista de cursos, que tenga el dominio de bsqueda de mas un botn, que
filtre los que no son mis cursos. Dejando como estandarte que no quede seleccionado al abrir la bsqueda o sea pasarlo por el context
Veamos la modificacin en la vista.
<!--Define Vista Search-->
<record model="ir.ui.view" id="view_openacademy_course_search">
<field name="name">view.openacademy.course.search</field>
<field name="model">openacademy.course</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Course Search">
<filter string="My Courses" icon="terp-partner"
name="my_courses"
domain="[('responsible_id','=',uid)]"
help="My own ideas"/>
<filter string="Not my Courses" icon="terp-partner"
name="not_my_courses"
domain="[('responsible_id','&lt;&gt;',uid)]"
help="Other people ideas"/>
<field name="name"/>
</search>
</field>
</record>

Lo unico que hacemos es crear un nuevo atributo filter con la nueva bsqueda, resalvando que utilizamos

'&lt;&gt;'

que es la forma de representar el < y > para que no

tengamos problemas de interpretacin de la vista.


Editamos el action para que deje este filtro deshabilitado.
<!--Acciones-->
<record model="ir.actions.act_window" id="action_openacademy_course_form">
<field name="name">Courses</field>
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_openacademy_course_search"/>
<!--Esta declaracin es para contexto. La sintaxis es nica dentro de openERP
y debe ser search_default + el_nombre_del_filtro_declarado_en_la_vista_search-->
<field name="context">{'search_default_my_courses': 1, 'search_default_not_my_courses': 0}</field>
<!--<field name="limit">100</field>-->
</record>

155

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 28

Editar la vista search de cursos, que tengamos una agrupacin de los registros por
responsable y tambin agrupacin por nombre.

De forma muy similar a la utilizacin de la tag filter para filtar registros con el auxilio de los dominios, podremos agrupar los registros con el auxilio del context.
<!--Define Vista Search-->
<record model="ir.ui.view" id="view_openacademy_course_search">
<field name="name">view.openacademy.course.search</field>
<field name="model">openacademy.course</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Course Search">
<filter string="My Courses" icon="terp-partner"
name="my_courses"
domain="[('responsible_id','=',uid)]"
help="My own ideas"/>
<filter string="Not my Courses" icon="terp-partner"
name="not_my_courses"
domain="[('responsible_id','&lt;&gt;',uid)]"
help="Other people ideas"/>
<separator orientation="vertical"/>
<field name="name"/>
<newline/>
<group expand="0" string="Group By...">
<filter string="Responsible" icon="terp-personal" domain="[]" context="{'group_by' :
'responsible_id'}"/>
<filter string="Name" icon="terp-personal" domain="[]" context="{'group_by' : 'name'}"/>
</group>
</search>
</field>
</record>

156

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 29

Crear los archivos de datos para el mtodo de seguridad del Modulo openacademy,
tendriamos 2 grupos openacademy Manager e openacademy user, y que los usuario
no puedan borrar registros.

Tenemos que crear 2 archivos, tenemos que empezar por el xml que contiene los
grupos, para mantener la organizacin del modulo creamos una carpeta llamada security

donde

tendremos

los

dados

necesarios

creamos

el

xml

security/openacademy_security.xml.
<?xml version="1.0"?>
<openerp>
<data noupdate="0">
<!-Users Groups
-->
<record model="res.groups" id="group_openacademy_manager">
<field name="name">OpenAcademy / Manager</field>
</record>
<record model="res.groups" id="group_openacademy_user">
<field name="name">OpenAcademy / User</field>
</record>
</data>
</openerp>

Vemos que tenemos 2 registros

para la tabla (clase) res.groups con los id-xml

group_openacademy_manager y group_openacademy_user y los campos name con


la descripcin de los mismos.
Luego creamos el archivo con los permisos propiamente dicho. Por se tratar de un archivo CSV el orm solo acepta si el archivo tenga el nombre de la clase en este caso seria security/ir.model.access.csv.
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
openacademy_session_user,openacademy_session_user,model_openacademy_session,openacademy.group_openacademy_user,1,1,1,0
openacademy_session_manager,openacademy_session_manager,model_openacademy_session,openacademy.group_openacademy_manager,1,1,1,1
openacademy_course_user,openacademy_course_user,model_openacademy_course,openacademy.group_openacademy_user,1,1,1,0
openacademy_course_manager,openacademy_course_manager,model_openacademy_course,openacademy.group_openacademy_manager,1,1,1,1
openacademy_attendee_user,openacademy_attendee_user,model_openacademy_attendee,openacademy.group_openacademy_user,1,1,1,0
openacademy_attendee_manager,openacademy_attendee_manager,model_openacademy_attendee,openacademy.group_openacademy_manager,1,1,1,1

Aqui creamos los acesos, para las 3 clases (session, course, attendee) para los 2
grupos (openacademy manager y openacademy user), analisando los datos arriba tenemos como falso la permisin de borrar (unlink) para os usuarios en las 3 clases.

157

8. RESOLUCIN DE LOS EJERCICIOS.


Ademas de crear los archivos arriba tenemos tambien que informar al modulo que
tenemos datos a seren agregados al modulo. Asemos las modificaciones abajo al archivo _openerp_.py
{

# ESTA ES LA DEFINICION DEL MODULO


# los datos que se definen aqu, son los que aparecen en la opcin de
# importacin de modulos en el cliente de openERP
"name": "Open Academy",
"version": "1.0",
# Aqu van todas las dependencias que tenga el modulo
"depends": ["base"],
"author": "Felipe Chiu",
"category": "Test",
# Aqu debe escribirse una breve descripcin de lo que hace el mdulo.
"description": """
Open Academy module for managing trainings:
- training courses
- training sessions
- attendees registration
""",
# Aqu se declaran todos los xml que contienen la definicin de los menus,
# acciones y vistas que utilizan el mdulo.
'update_xml': ['openacademy_view.xml',
'partner_view.xml',
'security/openacademy_security.xml',
'security/ir.model.access.csv',],
'demo': [],
'installable': True,
'active': False,
'certificate': False,

158

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 30

Crear un workflow para la clase session, que contenga los estados draft, confirm y
done.
Lo primero necesario para crear el workflow de cambio de estado, es crear el campo nuevo en la clase, session, que contenga los cambios de estado como sigue abajo.
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
'instructor_id': fields.many2one('res.partner', 'Instructor'),
'remaining_seats_percent':fields.function(_remaining_seats_percent,
method=True,type='float',
string='Remaining seats'),
'attendee_count': fields.function(_get_attendee_count, type='integer', string='Attendee Count',
method=True),
# state es una palabra reservada - se pasan tuplas como datos a mostrar
# los elementos de la tupla se definen (valor_a_guardar_en_tabla, texto_a_mostrar_al_usuario)
# al final se escribe el label del control.
'state' : fields.selection([('draft','Draft'),('confirm','Confirm'),('done','Done'),], 'State',
readonly='True') ,
'active': fields.boolean('Active', help="Un-Check this field for hidden record."),
}

Creamos un campo com en nombre state que es una palabra reservada de openerp
justamente usada siempre en los workflow, no debriamos utilizar otro nombre. Este
campo sera de tipo selection que va contener los 3 estados Draft (borrador), confirm
(confirmado), done (terminado), el campo sera readonly ya que va ser administrado
por el workflow.
Tambin tenemos que cambiar el parametro del codigo _defaults para que al guardar el registro se grabe con draft.
_defaults = {
'active': True,
'state': 'draft',
'start_date': lambda *a: time.strftime('%Y-%m-%d'),
}

Ademas tenemos que crear los metodos que van ser disparados por el workflow
que va definitivamente cambiar los estados.
def signal_confirm(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
return True
def signal_done(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'done'}, context=context)
return True

El codigo arriba simplemente utilizando del metodo write cambia el estado de Draft para confirm en el primer metodo y en el segundo cambia de de confirm a done esto
siguiendo la logica que vamos implementar en el xml del workflow.

159

8. RESOLUCIN DE LOS EJERCICIOS.


Tenemos ahora que crear el archivo xml de definicin del xml, para una correcta
administracin del modulo vamos crear la carpeta workflow y adentro vamos crear el
archivo openacademy_workflow.xml.
Vamos inicialmente poner en el archivo la declaracin de workflow.
<?xml version="1.0" ?>
<openerp>
<data>
<!--workflow definition-->
<record id="workflow_openacademysessionworkflow0" model="workflow">
<field name="on_create"/> True</field>
<field name="name">openacademy_session_workflow</field>
<field name="osv">openacademy.session</field>
</record>
</data>
</openerp>

En la declaracin tenemos el id que debe ser unico en todo el sistema luego la declaracin de model que siempre va ser workflow, Como queremos que el workflow
sea instanciado al agregamos un registro, definimos en on_create como 1 (true) luego definos el nombre del workflow y lo mas importante definimos en el field OSV
cual clase el workflow va estar relacionado.
Luego tenemos que definir las activity (actividades) del workflow que son los circulos con los procesos que sern ejecutados, lo importante en las definicines de las
actividades es que el action de la misma no es obligatorio y como nuestro workflow
va tratar apenas tratar transado de estado en primer nodo (activity) na va tener definido un accin. Esto se da porque definimos que en los defaults ya ponga como valor
estandart en el campo state como draft caso contrario tambin se podra cambiar por
una atividad del workflow. Veamos el codigo:
<!--activity-->
<record id="workflow_activity_draft0" model="workflow.activity">
<field name="kind">dummy</field>
<field name="name">draft</field>
<field name="join_mode">XOR</field>
<field model="workflow" name="wkf_id" ref="workflow_openacademysessionworkflow0"/>
<field eval="0" name="flow_stop"/>
<field name="split_mode">XOR</field>
<field eval="1" name="flow_start"/>
</record>
<record id="workflow_activity_confirm0" model="workflow.activity">
<field name="kind">function</field>
<field name="name">confirm</field>
<field name="join_mode">XOR</field>
<field model="workflow" name="wkf_id" ref="workflow_openacademysessionworkflow0"/>
<field eval="0" name="flow_stop"/>
<field name="split_mode">XOR</field>
<field name="action">signal_confirm()</field>
<field eval="0" name="flow_start"/>
</record>
<record id="workflow_activity_done0" model="workflow.activity">
<field name="kind">function</field>
<field name="name">done</field>
<field name="join_mode">XOR</field>
<field model="workflow" name="wkf_id" ref="workflow_openacademysessionworkflow0"/>
<field eval="1" name="flow_stop"/>
<field name="split_mode">XOR</field>
<field name="action">signal_done()</field>
<field eval="0" name="flow_start"/>
</record>

160

8. RESOLUCIN DE LOS EJERCICIOS.


En la primera actividad del workflow, tenemos los atributos id que puede ser siguiendo

los

standarts

que

tenga

el

nombre

workflow_activity_<nombre>_<seguido_de_un_numero>, el atributo model siempre


sera workflow.activity, luego el fue definido el kind como dummy o sea la activity
no hace nada no ejecuta ningun codigo o accin como explique mas arriba, el atributo
name que le dimos draft, atributo join_mode y split_mode como XOR que es el estandart y como vamos tener solamente 1 transition entre cada actividad no afecta el cambio, luego tenemos la linea
flow0"/>

<field model="workflow" name="wkf_id" ref="workflow_openacademysessionwork-

que es especifica que la actividad corresponde al workflow con el ID work-

flow_openacademysessionworkflow0, definimos el flow_stop como falso y el


flow_start como true ya que es el primer nodo del workflow.
Note que estamos en este ejercicio definiendo de otra forma como explicado en el
manual mas arriba la forma de definir el atributo, en vez de utilizar la notacion true o
false usamos el atributo eval como en esta linea
demos escribir tambien as:

<field eval="0" name="flow_stop"/>

<field name=flow_stop/>False</field>

que po-

esto para quien ya tiene ex-

periencia con html o xml ya lo sabe pero mejor dejar explicado.


En las otras 2 actividades solamente tenemos agregado el atributo action que es la
llamada al codigo python que ara el cambio del campo state por ejemplo a confirm
llamando el mtodo signal_confirm() y en la ultima actividad decirle que es el final
del fluxo pasando como true el atributo flow_stop.
Luego definimos las transacciones entre las actividades que en nuestro caso sern
2.
<!--trasition-->
<record id="workflow_transition_1" model="workflow.transition">
<field name="signal">signal_confirm</field>
<field model="workflow.activity" name="act_from" ref="workflow_activity_draft0"/>
<field model="workflow.activity" name="act_to" ref="workflow_activity_confirm0"/>
<field name="condition">True</field>
</record>
<record id="workflow_transition_0" model="workflow.transition">
<field name="signal">signal_done</field>
<field model="workflow.activity" name="act_from" ref="workflow_activity_confirm0"/>
<field model="workflow.activity" name="act_to" ref="workflow_activity_done0"/>
<field name="condition">True</field>
</record>

Como en la definicin de actividades tambin pusimos todos los atributos existentes para las transacciones. Las transacciones de este ejemplo es bastante simples definimos los atributos id y el model el id solamente no puede ya existir en todo el sistema y el model debe ser si o si workflow.transition. Luego definimos el signal que va
ser enviado en nuestro caso por los botones disponible en el form presionado por el
usuario que va decir cuando ejecutar la transicin, en la primeira el signal enviado
161

8. RESOLUCIN DE LOS EJERCICIOS.


sera signal_confirm, definimos ademas el origen y destino de la transacin por los
atributos act_form e act_to y por ultimo el atributo condition que en nuestro caso esta
como true que quiere decir que no va a tener condiccin ya que pasando true siempre
se ejecutara.
Tambin tenemos que agregar en el formulario el botn que va dispara el workflow, vamos en la vista formulario de la clase session agregar el botn para el workflow cambie el estado del documento.
<!--Define Vista formulario-->
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats" on_change="onchange_remaining_seats(seats, attendee_ids)"/>
<field name="attendee_count"/>
<field name="course_id"/>
<field name="remaining_seats_percent" widget="progressbar"/>
<field name="active"/>
<field name="instructor_id" domain="['&amp;', ('is_instructor', '=', True), ('supplier','=',True) ]"/>
<field name="attendee_ids" colspan="4" on_change="onchange_remaining_seats(seats, attendee_ids)">
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees" editable="top">
<field name="partner_id"/>
</tree>
</field>
<field name="active" />
<group colspan="4" col="4">
<field name="state" />
<button string="Mark to confirm" type="workflow" name="signal_confirm" states="draft" />
<button string="Mark as done" type="workflow" name="signal_done" states="confirm" />
</group>
</form>
</field>
</record>

En el botn tenemos el type que dice workflow el name que es la senal que va pasar al transaction del workflow y el estado que tiene el objeto antes del click o sea
arriba el manda la senha signal_confirm si el states es draft o manda signal_done si el
states es confirm.
Ya tenemos nuestro workflow ja implementado pero todavia no va funcionar si no
declaramos el xml del workflow en el modulo. Para eso editamos el archivo __openerp__.py, y agregamos el xml la parametro update_xml.

162

8. RESOLUCIN DE LOS EJERCICIOS.


{
"name": "Open Academy",
"version": "1.0",
"depends": ["base"],
"author": "Felipe Chiu",
"category": "Test",
"description": """
Open Academy module for managing trainings:
- training courses
- training sessions
- attendees registration
""",
'update_xml': ['openacademy_view.xml',
'partner_view.xml',
'security/openacademy_security.xml',
'security/ir.model.access.csv',
'workflow/openacademy_workflow.xml',],
'demo': [],
'installable': True,
'active': False,
'certificate': False,
}

163

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 31

Agregar al workflow que solamente permita cambiar al estado a confirmado si la fecha de la seccion sea igua o mayor que da fecha actual.
Nota: para ejecutar un exception en openerp usamos:
raise osv.except_osv( 'Error !', 'mensaje' )
Apenas cambiamos el metodo que es llamado por primero en nuestro workflow
que es el metodo signal_confirm.
def signal_confirm(self, cr, uid, ids, context={}):
for session in self.browse(cr, uid, ids, context=context):
if session.start_date < time.strftime('%Y-%m-%d'):
raise osv.except_osv( 'Error !', 'Start Date minor to now' )
self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
return True

Aqu ya es importante relacionar algunos conceptos importantes openerp permite trabajar con varios registros a la vez, por exemplo si tenemos este metodo llamado por
un actin general los mtodos debern estar preparado para tratar los varios registros
seleccionados por el usuario, por eso for ejemplo tratamos siempre con un bucle for
que va funcionar con 1 o varios registros. En nuestro caso arriba agarra todos los registros seleccionados y que son enviados al workflow verifica uno a uno si la fecha es
valida o sea si la fecha de la seccion es menor que la fecha actual si es asi envia un
raise que e una excepcin del python y cancela y hace el rowback de la transation.

164

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 32

Crear un wizard que agregue en la clase session los participantes.


Es un wizard que para no tener que editar una session para agregar participantes
(attendees) lo haremos por el wizard. Incluso puede ser utilizado para agregar por
ejemplo una lista de participantes a varias sessiones ya cargadas, digamos que tenemos los participantes pedro y paulo que van a participar de 2 sessiones openerp tecnical y openerp funcional, si vamos por el mecanismo tradicional tendriamos que editar
2 sessiones para los 2 participantes ya atravs del wizard lo haremos de una sola vez.
A fines de fines didcticos traeremos al wizard el campo session del primer registro que se encuentra en la lista tree seleccionada pero el mismo no ser utilizado para
agregar por ejemplo los participantes.
Antes de todo tenemos que crear la carpeta para los wizard de nuestro modulo.
Crearemos la carpeta wizard y dentro de esta carpeta tenemos que agregar un archivo
__init__.py que se utilizara para declarar los archivos .py de los wizard.
Archivo openacademy/wizard/__init__.py
import create_attendee

Luego creamos el archivo openacademy/wizard/create_attendee.py


Archivo openacademy/wizard/create_attendee.py
from osv import osv
from osv import fields
class openacademy_create_attendee_wizard(osv.osv_memory):
_name = 'openacademy.create.attendee.wizard'
_columns = {
'attendee_ids': fields.one2many('openacademy.attendee.memory', 'wiz_id', 'Attendees'),
'session_id': fields.many2one('openacademy.session', 'Session'),
}
def _get_active_session(self, cr, uid, context=None):
print "context de defaults", context
if not context:
context = {}
if not context.get('active_model', False) == 'openacademy.session':
return False
return context.get('active_id', False)
_defaults = {
'session_id': _get_active_session,
}
def action_add_attendee(self, cr, uid, ids, context=None):
print "Este es el contexto", context
wizard_data = self.browse(cr, uid, ids[0], context=context)
los_ids = context.get('active_ids')
attendee_db = self.pool.get('openacademy.attendee')
for data in los_ids:
for attendee in wizard_data.attendee_ids:
attendee_db.create(cr, uid, {
#'name': attendee.name,
'partner_id': attendee.partner_id.id,
'session_id': data,
}, context=context)
return {}

165

8. RESOLUCIN DE LOS EJERCICIOS.


openacademy_create_attendee_wizard()
class openacademy_attendee_memory(osv.osv_memory):
_name = 'openacademy.attendee.memory'
_columns = {
'name': fields.char('Name', size=64),
'partner_id': fields.many2one('res.partner', 'Partner'),
'wiz_id': fields.many2one('openacademy.create.attendee.wizard', 'Wizard'),
}
openacademy_attendee_memory()

Tenemos los mismos import que en la definicin de la clase osv.osv, luego la definicion de 2 clases heredadas de osv.osv_memory pero como si fueran amb.
Lo mas importante en esta clase son los 2 metodos declarados _get_action_session
y action_add_attendee.
Analisando el codigo de la clase

def _get_active_session(self, cr, uid, context=None):

En esta

clase openerp utiliza de context como parametro que en nuestro caso va traer los registros selecionado o posicionado en el momento de llamar el wizard. En la clase para
facilitar la comprensin en el codigo pasamos un print context que nos va listas el
contenido del context un exemplo seria:
context de defaults {'lang': u'en_US', u'active_ids': [6], 'tz': False, 'uid': 1, u'active_model':
u'openacademy.session', 'project_id': False, u'active_id': 6}

Vamos esclarecer algunas cosas, en openerp es posible tener traducciones no solamente del campo label como tambin del contenido del campo, por ejemplo, un registro de de un producto puede tener su descripcin diferente para cada idioma definido al usuario. Si vemos en el context nos muestra que el registro es del lang en_US.
Luego tambin nos pasa que los registros activos si son multiplos (varias selecciones),
si el contendio de datos esta compactado, el uid que es el ID del usuario del sistema
que en nuestro ejemplo es el admin, active_model que es el nombre del modulo, si
tiene codigo de projecto el modulo, y el registro actual posicionado en la base de datos.
En los campos tipo string notamos el u antes de las descripciones esto es como python maneja para definir que el string es uft-8.
En la llamada del metodo context.get pasamos el registro que queremos que es el
valor de active_id, y tenemos un en el codigo de pasando como parametro luego la expresin false como abajo:
context.get('active_id', False)

Esto va traer el valor de este campo que esta en el contexto y el segundo parametro
es si se trata del registro actual o podriamos pasar el id del registro como queremos
que sea el registro actual pasamos false.

166

8. RESOLUCIN DE LOS EJERCICIOS.

Bueno con las explicacones arriba analisamos otra vez el codigo


def _get_active_session(self, cr, uid, context=None):
print "context de defaults", context
if not context:
context = {}
if not context.get('active_model', False) == 'openacademy.session':
return False
return context.get('active_id', False)
_defaults = {
'session_id': _get_active_session,
}

verifica si tenemos datos en el context si esta vazio quiere decir que no esta seleccionado ningun registro verifica si es llamado por la clase openacademy.session e retorna false, que genera una except del orm y presenta una mensaje que es necesario
seleccionar algun registro.
Si un registro esta activo trae el campo active_id que es el ID del registro selecionado como este metodo es llamado por un _defaults completa el campo session_id
con el ID retornado.
El segundo metodo se ejecuta una vez clicado en el OK el mismo agarra los participantes cargados en el wizard y agrega en los registro seleccionados o en el registro
actual.
Luego vamos a definir la vista, action, y la definicin de este wizard en los 2 menus diferentes.
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_openacademy_create_attendee_wizard_form">
<field name="name">view.openacademy.create.attendee.wizard.form</field>
<field name="model">openacademy.create.attendee.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Add Attendee">
<field name="session_id"/>
<newline/>
<field name="attendee_ids" colspan="4">
<tree string="Attendees" editable="bottom">
<field name="partner_id"/>
</tree>
</field>
<button type="object" name="action_add_attendee" string="_Add Attendees" icon="gtk-ok"
confirm="Are you sure you want to add those attendees?"/>
<button type="special" special="cancel" string="_Cancel" icon="gtk-cancel"/>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_openacademy_create_attendee_wizard_form">
<field name="name">Add attendee</field>
<field name="res_model">openacademy.create.attendee.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem name="Add attendee" parent="menu_main_openacademy"
id="menu_action_openacademy_create_attendee_wizard_form"
action="action_openacademy_create_attendee_wizard_form"
/>
<act_window id="act_window_action_openacademy_create_attendee_wizard_form"
name="Add Attendees"
src_model="openacademy.session"
res_model="openacademy.create.attendee.wizard"
view_mode="form"
key2="client_action_multi"
target="new"
/>
</data>
</openerp>

167

8. RESOLUCIN DE LOS EJERCICIOS.


La vista es identica a la definicin de las vistas de la clase osv.osv y tenemos los
botones de wizard definidos en rojo la primera definicin del boton es para que llame
el metodo pasado en el atributo name, y el seguindo es para si desea cancelar que pasa
una seal especial con el atributo type del tipo special.
Mas abajo tenemos el actin propriamente dicho que en lo diferente a todos los
otros actin tine el atributo target que esta definido a new que le dice a openerp que
debe lanzar en una nueva ventana.
Despus tenemos 2 maneras de llamar este actin en el menu principal del modulo
o por el sidebar que es el menu al costado derecho de las pantallas donde tenemos opciones de importar y exportar los registros.
Por el menu principal:
<menuitem name="Add attendee" parent="menu_main_openacademy"
id="menu_action_openacademy_create_attendee_wizard_form"
action="action_openacademy_create_attendee_wizard_form"
/>

O por el sidebar:
<act_window id="act_window_action_openacademy_create_attendee_wizard_form"
name="Add Attendees"
src_model="openacademy.session"
res_model="openacademy.create.attendee.wizard"
view_mode="form"
key2="client_action_multi"
target="new"
/>

Observamos el atributo key2=client_action_multi sirve para que el llamado al


wizard ademas de estar en el sidebar tambien es llamado por el boton action que esta
por ejemplo en el cliente GTK. Si Este parametro es omitido solamente tenemos el acceso por el sidebar.

168

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 33

Corregir el wizard llamado por el men principal del modulo que genera error.

Como creamos un acceso del wizard tambin por el menu principal del modulo, tenemos que modificar le mtodo action_add_attendee ya que el mismo esta preparado
solamente para ser llamado cuando pasado en el context los registros a guardar los
participantes, pero como al ser llamado por el men principal estos datos no son pasados.
def action_add_attendee(self, cr, uid, ids, context=None):
print "Este es el contexto", context
if not context:
context = {}
wizard_data = self.browse(cr, uid, ids[0], context=context)
attendee_db = self.pool.get('openacademy.attendee')
if context.get('active_model') == 'openacademy.session':
los_ids = context.get('active_ids')
for data in los_ids:
for attendee in wizard_data.attendee_ids:
attendee_db.create(cr, uid, {
#'name': attendee.name,
'partner_id': attendee.partner_id.id,
'session_id': data,
}, context=context)
else:
for attendee in wizard_data.attendee_ids:
attendee_db.create(cr, uid, {
'partner_id': attendee.partner_id.id,
'session_id': wizard_data.session_id.id,
}, context=context)
return {}

Basicamente lo que hicimos es verificar si se esta pasando en el context registros


pre seleccionados si no pide para selecionar una session ya existente.

169

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 34

En el Wizard creamos que el mismo sea posible ser llamado cuando seleccionamos
varios registro o apenas 1 registro de la clase session cuando seleccionado varios registros en nos trae la sessin de primer registro seleccionado en el tree, haora vamos
implementar que verifique si tenemos varios registros seleccionados y ocultamos
(atributo invisible) el campo session en la vista de este wizard.
Para aplicar este funcionamiento simplesmente cambiamos la vista del wizard con
el codigo abajo en el archivo openacademy/wizard/create_attendee_view.xml:
<record model="ir.ui.view" id="view_openacademy_create_attendee_wizard_form">
<field name="name">view.openacademy.create.attendee.wizard.form</field>
<field name="model">openacademy.create.attendee.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Add Attendee">
<field name="session_id" invisible="len(context.get('active_ids',[]))&gt;1"
/>
<newline/>
<field name="attendee_ids" colspan="4">
<tree string="Attendees" editable="bottom">
<field name="partner_id"/>
</tree>
</field>
<button type="object" name="action_add_attendee" string="_Add Attendees" icon="gtk-ok"
confirm="Are you sure you want to add those attendees?"/>
<button type="special" special="cancel" string="_Cancel" icon="gtk-cancel"/>
</form>
</field>
</record>

Poniendo el codigo sin los caracteres de conversin de html quedaria


invisible=len(context.get('active_ids',[]))>1
O sea deja el campo session_id invisible cuanto la cantidad de itens en el dictionario del context el elemento active_id sea mayor que 1, esto representa la funcionalidad
de que si tenemos varios registros seleccionados no nos muestra la session.
NOTA: Agregar al principio del archivo en la primeira linea de la vista openacademy/wizard/create_attendee_view.xml la linea:
# -*- coding: utf-8 -*Esto es para que no tengamos problemas con los caracteres acentuados.

170

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 35

Crear un Dashboard para el modulo openacademy mostrando las vistas asociadas a


los actions act_session_graph, act_course_list y act_session_calendar.
Definimos inicialmente el nombre del archivo xml para el dashboard, la grand mayoria utiliza la notacin board_module_o_clase_view.xml entonces el archivo se llamara board_session_view.xml, luego tenemos que editar el archivo __openerp__.py
para que contenga tanto el nombre del xml como su dependencia.
{

# Este es el archivo responsable por la descripcin del modulo


# Los dados aqu presente sern mostrado en las opciones de importacin
# del modulo en OpenERP
"name": "Open Academy",
"version": "1.0",
# Dependencias del modulo
"depends": ["base","board"],
"author": "Fadel Damen Schreiner",
"category": "Academy",
# Aqu debe escribirse una breve descripcin de lo que hace el mdulo.
"description": """
Open Academy Modulo para mantenimiento de entrenamiento:
- Cursos
- Secciones
- Participantes (registro)
""",
# Aqu se declaran todos los xml que contienen la definicin de los menus,
# acciones y vistas que utilizan el mdulo.
'update_xml': ['openacademy_view.xml',
'partner_view.xml',
'security/openacademy_security.xml',
'security/ir.model.access.csv',
'openacademy_workflow.xml',
'wizard/create_attendee_view.xml',
'board_session_view.xml'],
'demo': [],
'installable': True,
'active': False,
'certificate': False,

Una observacin importante como las vistas son basadas en los action y no en la
vista propriamente dicha, y normalmente en una vista hacemos que tenga 1 actin
para varias tipos de vistas, por exemplo en nuestro modulo para session tenemos 1 action pero es el mismo para todos los tipos de vista, tipo graph, tree, form, calendar etc.
Veamos el action que tenemos en la vista:
<record model="ir.actions.act_window" id="action_openacademy_session_form">
<field name="name">sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,gantt,graph</field>
<field name="search_view_id" ref="view_openacademy_session_search"/>
<!--<field name="limit">100</field>-->
</record>

Ai tenemos una action para varias vistas

name="view_mode">tree,form,calendar,gantt,graph

Para

poder utilizar en el dashboard tenemos que crear los action para cada una de las vistas
mostrada en el dashboard.

171

8. RESOLUCIN DE LOS EJERCICIOS.


Creemos primeramente los action para cada una de las vistas como abajo:

<record model="ir.actions.act_window" id="act_session_graph">


<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">graph</field>
<field name="view_id" ref="openacademy.view_openacademy_session_graph"/>
</record>
<record model="ir.actions.act_window" id="act_session_calendar">
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">calendar</field>
<field name="view_id" ref="openacademy.view_openacademy_session_calendar"/>
</record>
<record model="ir.actions.act_window" id="act_course_list">
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>

Lo mas importante son las sineas que hacen referencia a las vistas en si. Por ejemplo openacademy.view_openacademy_session_graph que es el nombre de la vista
creada en el archivo openacademy_view.xml. Lo mismo hacemos con las otras 2 vistas que queremos en nuestro dashboard.
Despues creamos el dashboard propriamente dicho:
<record model="ir.ui.view" id="board_session_form">
<field name="name">Session Dashboard Form</field>
<field name="model">board.board</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Session Dashboard">
<board style="2-1">
<column>
<action name="%(act_session_graph)d" string="Attendees by course" />
<action name="%(act_course_list)d" string="Courses" />
</column>
<column>
<action name="%(act_session_calendar)d" string="Sessions" />
</column>
</board>
</form>
</field>
</record>

Luego creamos el action del dashboard:


<record model="ir.actions.act_window" id="open_board_session">
<field name="name">Session Dashboard</field>
<field name="res_model">board.board</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="usage">menu</field>
<field name="view_id" ref="board_session_form"/>
</record>

Y luego creamos el menu para el mismo:


<menuitem id="menu_dashboard"
name="Dashboard"
sequence="0"
parent="openacademy.menu_main_openacademy"
action="open_board_session"
/>

172

8. RESOLUCIN DE LOS EJERCICIOS.


Para mejor referencia y consulta pongamos el codigo completo de la vista:
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.actions.act_window" id="act_session_graph">
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">graph</field>
<field name="view_id" ref="openacademy.view_openacademy_session_graph"/>
</record>
<record model="ir.actions.act_window" id="act_session_calendar">
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">calendar</field>
<field name="view_id" ref="openacademy.view_openacademy_session_calendar"/>
</record>
<record model="ir.actions.act_window" id="act_course_list">
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<record model="ir.ui.view" id="board_session_form">
<field name="name">Session Dashboard Form</field>
<field name="model">board.board</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Session Dashboard">
<board style="2-1">
<column>
<action name="%(act_session_graph)d" string="Attendees by course" />
<action name="%(act_course_list)d" string="Courses" />
</column>
<column>
<action name="%(act_session_calendar)d" string="Sessions" />
</column>
</board>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="open_board_session">
<field name="name">Session Dashboard</field>
<field name="res_model">board.board</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="usage">menu</field>
<field name="view_id" ref="board_session_form"/>
</record>
<menuitem id="menu_dashboard"
name="Dashboard"
sequence="0"
parent="openacademy.menu_main_openacademy"
action="open_board_session"
/>
</data>
</openerp>

173

8. RESOLUCIN DE LOS EJERCICIOS.


Ejercicio 36

Hacer todo lo necesario para que nuestro modulo openacademy este totalmente al
idioma espaol paraguay.
En nuestro modulo tenemos 3 archivos python que podran tener expresiones que
no fueron pasada para el modulo de traducciones de openerp. Serian openacademy.py,
partner.py y wizard/create_attendee.py.
Todo lo que sea string tenemos que utilizar la funcin _(), para que openerp pueda
guardar para que sea traducido los mensajes.
Aqu esta el archivo openacademy.py con las modificaciones necesarias.
# -*- coding: utf-8 -*from osv import osv
from osv import fields
import time
from tools.translate import _
class openacademy_course(osv.osv):
_name = 'openacademy.course'
def _get_attendee_count(self, cr, uid, ids, field, arg, context=None):
res = {}
session_obj = self.pool.get('openacademy.session')
course_obj = self.pool.get('openacademy.course')
courses = course_obj.browse(cr, uid, ids, context=context)
for course in courses:
attendee_cont = 0
for session in course.session_ids:
attendee_cont += len( session.attendee_ids )
res[ course.id ] = attendee_cont
return res
_columns = {
'name' : fields.char('Name', size=128, required=True),
'description' : fields.text('Description'),
'responsible_id' : fields.many2one('res.users', 'Responsible'),
'session_ids': fields.one2many('openacademy.session', 'course_id', 'Sessions'),
'attendee_count': fields.function(_get_attendee_count, type='integer', string=_('Attendee Count'),
method=True),
}
def _check_description(self,cr,uid,ids,context=None):
courses = self.browse(cr,uid,ids,context=context)
check = True
for course in courses:
if course.name==course.description:
return False
return check
_constraints = [(_check_description, _('Please use a different description'), ['name','description'])]
_sql_constraints = [('unique_name', 'unique(name)', _('Course Title must be unique')),]
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
course_obj = self.pool.get('openacademy.course')
course = course_obj.browse(cr, uid, id, context=context)
new_name = course.name + ' (copy)'
list = course_obj.search(cr, uid, [('name','like',new_name)], context=context)
if len(list)>0:
new_name='%s (%s)' % (new_name,len(list)+1)
default.update({'name': new_name})
new_id = super(openacademy_course, self).copy(cr, uid, id, default, context=context)
return new_id
openacademy_course()
class openacademy_session(osv.osv):
_name = 'openacademy.session'
def onchange_remaining_seats(self, cr, uid, ids, seats, attendee_ids):
remaining_seats_percent = self._get_remaining_seats_percent(seats, attendee_ids)
res = {}
res.update({
'value': {
'remaining_seats_percent': remaining_seats_percent,

174

8. RESOLUCIN DE LOS EJERCICIOS.


}
})
if seats < 0:
res.update({
'warning': {
'title': 'Warning',
'message': _('You cannot have negative seats'),
}
})
session_obj = self.pool.get('openacademy.session')
session = session_obj.browse(cr, uid, ids, context=None)[0]
res['value']['seats'] = session.seats
res['value']['remaining_seats_percent'] = session.remaining_seats_percent
return res
def _get_remaining_seats_percent(self, seats, attendee_list):
return seats and ((100.0 * (seats - len(attendee_list)))/ seats) or 0
def _remaining_seats_percent(self, cr, uid, ids, field, arg, context=None):
result = {}
sessions = self.browse(cr, uid, ids, context=context)
for session in sessions :
result[session.id] = self._get_remaining_seats_percent(session.seats, session.attendee_ids)
return result
def _get_attendee_count(self, cr, uid, ids, field, arg, context=None):
res = {}
session_obj = self.pool.get('openacademy.session')
sessions = session_obj.browse(cr, uid, ids, context=context)
for session in sessions:
res[session.id] = len( session.attendee_ids )
return res
def signal_confirm(self, cr, uid, ids, context={}):
"""
cambia el estado de la session al presionar un boton
"""
for session in self.browse(cr, uid, ids, context=context):
if session.start_date < time.strftime('%Y-%m-%d'):
raise osv.except_osv( 'Error !', _('Start Date minor to now') )
self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
return True
def signal_done(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'done'}, context=context)
return True
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help=_('Duration in days')),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
'instructor_id': fields.many2one('res.partner', 'Instructor'),
'remaining_seats_percent':fields.function(_remaining_seats_percent,
method=True,type='float',
string=_('Remaining seats')),
'attendee_count': fields.function(_get_attendee_count, type='integer', string=_('Attendee Count'),
method=True),
'state' : fields.selection([('draft','Draft'),('confirm','Confirm'),('done','Done'),], 'State',
readonly='True'),
'active': fields.boolean('Active', help=_('Un-Check this field for hidden record')),
}
_defaults = {
'active': True,
'state': 'draft',
'start_date': lambda *a: time.strftime('%Y-%m-%d'),
}
openacademy_session()
class openacademy_attendee(osv.osv):
_name = 'openacademy.attendee'
_rec_name = 'partner.id'
_columns = {
'partner_id': fields.many2one('res.partner', 'Partner', required=True, ondelete='cascade'),
'session_id': fields.many2one('openacademy.session', 'Session', required=True, ondelete='cascade'),
}
openacademy_attendee()

175

8. RESOLUCIN DE LOS EJERCICIOS.


Luego vamos en el menu tools administracin - traducciones exportar importar
idiomas, seleccionamos el idioma ingles, y exportamos el archivo .po, luego agregamos este archivo en nuestro modulo, dentro de la carpeta i18n con el nombre .pot, e
copiamos luego para un archivo llamando es_PY.po y utilizamos cualquier herramienta para traducir al espaol paraguay, incluso puede utilizar un editor de texto plano
como el gedit. O un programa de linux el poedit.
Luego al instalar otra vez el modulo o levantar el servidor openerp con el parametro update=<nombre_modulo> openeerp va levantar la traduccin.
Otra herramienta utilizada para las traducciones que yo personalmente lo uso es el
Gtranslator me gusta porque posee memoria de traduccin o sea cuanto mas trminos
se traduce mas sugerencia nos trae.

176

Das könnte Ihnen auch gefallen