Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Índice de contenido
Desarrollo de módulos para Odoo 8..................................................................................................... 2
Elementos principales de un módulo............................................................................................... 3
El fichero __init__.py................................................................................................................. 4
El fichero __openerp__.py.......................................................................................................... 4
El fichero models.py................................................................................................................... 5
El fichero views/discografica.xml...............................................................................................7
Definición de vistas......................................................................................................................... 8
Definición del modelo: estableciendo campos/Field..................................................................... 11
Campos calculados.................................................................................................................... 11
Campos básico: selection.......................................................................................................... 12
Campos relacionados................................................................................................................ 13
Herencia......................................................................................................................................... 15
Extendiendo el modelo..............................................................................................................15
Extendiendo la vista.................................................................................................................. 17
Menús y acciones...........................................................................................................................19
Personalización de las vistas..........................................................................................................20
Informes......................................................................................................................................... 22
Definiendo un report................................................................................................................. 22
Definiendo una plantilla............................................................................................................23
Internacionalización.......................................................................................................................25
Vistas avanzadas............................................................................................................................ 25
Kanban...................................................................................................................................... 25
Graphs....................................................................................................................................... 25
Calendar.................................................................................................................................... 25
Workflows......................................................................................................................................25
Wizards.......................................................................................................................................... 25
Webservices................................................................................................................................... 25
Cuando instalamos un sistema, encontramos situaciones en las que no es posible cubrir todas las
necesidades del cliente con los componentes existentes (https://www.odoo.com/apps)
El sistema es flexible y nos ofrece mecanismos para ampliar su funcionalidad mediante desarrollos
propios que se integren con el resto de componentes.
En el apartado siguiente veremos la estructura que deben tener esos módulos para que sean
reconocidos e integrados por Odoo.
Antes de entrar en detalle, hay que recordar la arquitectura de Odoo que vimos en temas anteriores.
Como ya dijimos, sigue un patrón de diseño MVC (Modelo-Vista-Controlador).
Extraído de https://doc.odoo.com/6.0/developer/1_3_oo_architecture/mvc/
Este diagrama muestra líneas sólidas desde el controlador a la vista y al modelo indicando que tiene
acceso completo a ambos. Las líneas discontinuas reflejan el acceso limitado al controlador. Las
razones de este diseño son:
• De la Vista al Modelo: el modelo envía notificaciones a la vista cuando sus datos se
modifican para que la vista redibuje su contenido. El modelo no necesita saber nada del
funcionamiento interno que la vista necesita para llevar a cabo esta operación. Sin embargo,
la vista si necesita acceso a algunas partes internas del modelo.
• De la Vista al Controlador: la razón de que la vista tenga acceso limitado al controlador es
porque la relación de dependencia entre la vista y el controlador deben ser mínimas. El
controlador podría ser reemplazado en cualquier momento.
En Odoo esta implementación se realiza del siguiente modo:
• Modelo: tablas en PostgreSQL
Un módulo es un directorio situado dentro de un directorio de addons de Odoo (o cualquier otro que
especifiquemos en la configuración como, por ejemplo, extra-addons) que contiene al menos los
siguientes ficheros.
1. __init__.py
2. __openerp__.py
3. Ficheros definición de modelo/objeto.py (Business objects)
4. Fichero definición de vistas, acciones, workflows en xml (si fuese necesario)
5. Ficheros de datos ejemplo/iniciales en xml/csv(Data files)
6. Ficheros definición de controlador web (Web Controllers *** solo en v8)
7. Contenido estático a usar: imágenes, css, javascript (Static web objects)
Una vez configurado nuestro módulo, debemos ir a Odoo / Configuración / Actualizar lista de
módulos (recordad que esta opción solo aparece si tenemos las características técnicas habilitadas
para el usuario). Una vez actualizada la lista, ya lo tendremos disponible para instalar.
En la versión 8, Odoo incorpora un script para generar la estructura y ficheros necesarios para crear
un módulo. Estas técnicas, introducidas originalmente en lenguajes tipo Ruby, se conocen como
scaffolding
$ /opt/odoo8/odoo.py scaffold <module name> <where to put it>
Vamos a generar un módulo discografica que nos permita introducir discos en el sistema.
$ /opt/odoo8/odoo.py scaffold discografica extra-addons
El fichero __init__.py
Se ejecuta al cargar la aplicación. Se encarga de cargar los componentes necesarios para el módulo
(similar al init de un paquete en Python)
# -*- coding: utf-8 -*-
El fichero __openerp__.py
Contiene el manifiesto/descripción de nuestro módulo. Aparece información tipo: nombre,
descripción, versión, así como ficheros utilizados o dependencias que necesita cumplir.
# -*- coding: utf-8 -*-
'name': "Discografica",
'summary': """
'description': """
'author': "SGE2015",
'website': "http://www.iesdealquerias.es",
# Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml
'category': 'PrimerosPasos',
'version': '0.1',
'depends': ['base'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
'views/discografica.xml',
],
'demo': [
'demo.xml',
],
El fichero models.py
Dentro de este fichero podemos definir nuestros objetos de negocio. También es posible tener un
fichero por cada objeto de negocio que necesitemos. Debemos tener precaución a la hora de
crearlo para saber como acceder a él posteriormente con el namespace correcto.
El ORM de Odoo utiliza acceso jerárquico a los objetos, precediendo el nombre del módulo al
objeto. Así por ejemplo tendremos:
• purchase.order: orden de compra
• account.invoice: factura
Los objetos/modelos
Todos los objetos/modelos que definamos extienden models.Model.
from openerp import models,fields
class AModel(models.Model):
_name = “a.model.name”
field1 = fields.Char()
_name (obligatorio)
Nombre del objeto de negocio que se va a crear, es decir, la tabla en base de datos.
Debe especificarse con su namespace completo:
nombre_modulo.nombre_modelo
_inherit
Utilizado para heredar de un modelo. Lo veremos con posterioridad al estudiar los
distintos tipos de herencia
fields
Propiedades del objeto de negocio. (Anteriormente definidas en el atributo
_columns). Especificarán el nombre del atributo y definirán su tipo.
Por defecto, la etiqueta visible un campo será su nombre con letra capital pero se
puede especificar otra con el parámetro string.
field2 = fields.Integer(string="an other field")
def compute_default_value(self):
return self.get_value()
Dependiendo del tipo de datos que defina un Field - Char, Boolean, Integer, Float, Text, Selection,
Html, Date, Datetime – podrá disponer de otros parámetros específicos. Además de los tipos
mencionados anteriormente, también dispone de campos relacionales que veremos en el siguiente
apartado. En la documentación podemos encontrar todos los tipos admitidos para fields y sus
parámetros en el apartado Fields Reference
Al crear un modelo, Odoo creará por defecto unos campos automáticamente en cada tabla: id
(field), log_access, create_date (datetime), create_uid(res.users), write_date (datetime), write_uid
(res.users) que utilizará para llevar control de acceso a los objetos.
Fichero disco.py
# -*- coding: utf-8 -*-
class disco(models.Model):
_name = 'disco.disco'
author = fields.Char(string="Grupo/Banda",help="Grupo")
NOTA: En las versiones anteriores, el fichero que definía el modelo debía extender osv.osv y
acabar con una llamada al propio objeto. También utilizaba la propiedad _columns para definir los
campos del modelo pero actualmente se recomienda usar fields. Sigue funcionando por
compatibilidad pero debemos implementar el nuevo mecanismo. El antiguo mecanismo se
implementaría así:
class name_of_the_object(osv.osv):
_name = “name_of_the_object”
_columns = {…}
name_of_the_object()
El fichero views/discografica.xml
En este fichero determinamos los componentes de Odoo que se tienen que crear para que nuestro
modelo se integre en el sistema. Un módulo puede estar formado por vistas, gráficos, informes,
asistentes, flujos de trabajo, menús y acciones.
• L a vista es la representación gráfica del objeto en el navegador. Existen varios tipos de
vista, principalmente distinguimos vistas de árbol (tree) y vistas de formulario (form). Pero
también podemos definir vistas kanban, de búsquedas/filtros...
• El menú es el elemento que nos servirá para lanzar acciones. Una acción será una actividad
a realizar; abrir una ventana, generar un informe, etc.
• Un informe es la presentación organizada e imprimible de los datos.
• U n asistente es un conjunto de pasos secuenciales a realizar. Normalmente se usan para
configuraciones de los módulos.
• Un flujo de trabajo es la definición de la dinámica de los objetos. Cómo se crean nuevos,
se modifican los actuales...
</data>
</openerp>
Definición de vistas
En primer lugar se define un nombre para la vista y el modelo a consultar. Esto será común para
cualquier tipo de vista que creemos.
Para especificar el tipo de vista que deseamos implementar se define el campo arch de tipo xml y
con el siguiente tag establecemos si la vista es de tipo tree, form, etc. Todos los tags permitirán
personalizaciones mediante atributos; color, estilo... Buscaremos cada caso concreto en la referencia
de Odoo
Dentro de una vista, utilizaremos el tag <field name=”” /> para especificar qué campo del modelo
queremos mostrar. Mediante los parámetros de este tag podremos personalizar la apariencia del
mismo.
Vistas Formulario
Los tags que podemos usar en este tipo de vistas se dividen en dos tipos, aquellos que definen la
estructura del formulario y los que dan significado.
Estructurales
1. notebook: define secciones tabs/pestañas. Cada pestaña debe contener un elemento hijo
page.
2. page: se utiliza como contenedor de elementos. Puede tener los atributos:
1. string (obligatorio): Título de la pestaña
2. accesskey: acceskey HTML
3. attrs: atributos dinámicos basados en los valores de registros
3. group: se utiliza para agrupar campos. Por defecto una vista consta de 4 columnas, al definir
un grupo, los campos incluidos utilizarán automáticamente 2 columnas, una para la etiqueta
y otra para el valor del campo. Sin este tag, la etiqueta del campo no se muestra por defecto
y, en caso de quererla, tenemos que especificar el tag <label for=”campo”> para que se
muestre. El tag group se puede personalizar con los valores:
1. col: número de columnas
2. colspan: número de columnas a utilizar por un campo.
3. name: es útil en vistas extendidas ya que permite localizar mejor a los elementos.
4. string: permitirá mostrar un texto para los elementos del grupo.
Además de estos valores podemos hacer uso de etiquetas propias de html como <div>, <h1>, etc...
Semánticos
1. button: muestra un botón
a) icon: mostrar un icono
b) string: el texto del botón o el popup del mismo si hay definido un icon
c) type:
• workflow (defecto): envía una señal al workflow. El name del botón es la señal
pasada al workflow. El registro de fila se pasa junto a la señal de workflow
• object: llama a un método del modelo. El name del botón es el nombre del método.
El registro de la fila actual se pasa al método.
• action: carga y ejecuta una accion ir.actions. El name del botón es el id de una
acción definida.
d) name
e) args
f) attrs
g) states: requiere que el modelo defina el campo states. Combinado con attrs, debe
contener una lista de estados posibles. Si el botón no está en uno de esos estados no se
muestra.
h) context
i) confirm: mensaje a mostrar para al confirmar una acción.
<form>
<header> ... content of the status bar ... </header>
<sheet> ... content of the sheet ... </sheet>
<div class="oe_chatter"> ... content of the bottom part ... </div>
</form>
Independientemente del tipo de campo que definamos, podemos hacer uso de una serie de
parámetros comunes al declararlos (Fields Reference).
class openerp.fields.Field(string=None,**kwargs)
• string: etiqueta del campo
• help: tooltip
• readonly: default=False
• required: default=False
• index: si se quiere indexar en BBDD
• default: valor por defecto
• states: para utilizar en workflow. Lo veremos posteriormente
• groups: para restringir su acceso a los usuarios del grupo
• copy
• oldname
Campos calculados
Sirve para establecer un campo que debe mostrar un valor calculado, no obtenido de BBDD. Para
especificar que un campo es calculado podemos usar uno de los siguientes parámetros:
@api.depends('name')
def _compute_upper(self):
for rec in self:
self.upper = self.name.upper() if self.name else False
def _inverse_upper(self):
for rec in self:
self.name = self.upper.lower() if self.upper else False
Un campo selection es aquel al que podemos especificar una lista de valores que queremos que
muestre. Para usar un campo de este tipo debemos modificar nuestro modelo y, por ejemplo, definir
el campo author de la siguiente manera:
author = fields.Selection([("kiss","KISS"),("acdc","AC/DC")],"Grupo/Banda")
Como vemos se especifica una lista elementos tuplas (key,valor) y como último parámetro la
etiqueta del campo.
Una vez modificado el modelo, debemos modificar también la definición del campo en la vista. En
este caso, al field author tendremos que añadir el parámetro widget con el valor selection para
mostrar el campo como pretendemos. El parámetro widget nos permite modificar la apariencia de
los campos.
Tras realizar ambos cambios ya podemos tener una aproximación mejor al resultado que buscamos.
Debemos ser conscientes de las limitaciones de este método para mostrar una lista de valores de
entrada. La lista la insertamos estáticamente en código por lo que debemos estudiar si el caso en el
que nos encontramos sufrirá muchas modificaciones, requerirá de nuevos valores, etc. Si la
respuesta es afirmativa, parece que este mecanismo no será apropiado.
Una posible solución podría ser que la lista de valores se proporcionase por una función que, por
ejemplo, cargue los valores de un fichero de datos separados por comas. Para ello, basta con definir
una función en el modelo y llamarla al crear el campo selection.
def lista_grupos(self):
return [(“kiss”,”KISS”),(“acdc”,”AC/DC”),(“mrbig”,”MR.BIG”)]
author = fields.Selection(_lista_grupos,"Grupo/Banda")
A pesar de que esta aproximación nos proporciona algo más de flexibilidad, puede darse el caso de
que no se ajuste a nuestras necesidades. En ocasiones necesitaremos que el propio usuario pueda
añadir nuevas opciones y que éstas sean almacenadas en base de datos. Para ello, debemos de tener
otra tabla en base de datos y que nuestro campo esté relacionado con esta tabla de la que obtendrá
los valores.
Este mecanismo nos ofrece varias ventajas frente a la utilización de un simple campo selection;
integridad referencial (los campos selection no lo ofrecen) y el filtrado de campos (un campo
selection carga todos los valores en el html y no permite filtro alguno)
Campos relacionados
Para establecer campos relacionados con otro modelo disponemos de tres tipos de campos:
• One2many: el valor de este campo es un recordset de todos los registros del comodel de
modo que el campo inverse_name sea el registro actual. Obliga a que exista un campo
Many2one en el modelo relacionado
One2many(comodel_name,inverse_name, string, **kwargs)
• comodel_name: nombre del modelo destino (obligatorio)
• inverse_name: nombre del campo Many2one en el comodel
genre_ids = fields.Many2many("discografica.genero",
ondelete="cascade",required=True,string=”Estilos musicales”
Herencia
Extendiendo el modelo
En la imagen anterior podemos ver que existen dos modos de implementar la herencia, el modo
tradicional y el modo por delegación.
• Modo tradicional
◦ Herencia por extensión: este tipo de herencia se produce cuando las propiedades _name
e _inherit son iguales y se fijan a un objeto que ya existe en el sistema. Con este
mecanismo conseguimos añadir atributos a un objeto ya existente sin crear uno nuevo.
De este modo, las vistas existentes nos seguirán siendo válidas y las podremos extender
para completar los campos que deseemos.
Ejemplo: sobreescribimos el campo género para que sea Many2one y añadimos un
campo de opinión. Podemos seguir usando las mismas vistas y extenderlas para mostrar
el nuevo campo.
# -*- coding: utf-8 -*-
class discoExtendido(models.Model):
_name = 'disco'
_inherit = "disco"
genre = fields.Many2one("discografica.genero",ondelete="set
null",string="Estilo",help="Estilo")
◦ Herencia por prototipo: este tipo de herencia se produce cuando las propiedades _name
e _inherit son distintas, fijando _inherit a un objeto que ya existe en el sistema. De este
modo tenemos un objeto nuevo que hereda los componentes del padre pero que es
independiente. Las vistas existentes no servirán, por lo que habrá que crear nuevas
vistas.
Ejemplo: sobreescribimos el campo género para que sea tipo Many2one y añadimos un
campo precio al disco. Debemos crear una vista nueva para mostrar este modelo.
# -*- coding: utf-8 -*-
class disco2(models.Model):
_name = 'discografica.disco2'
_inherit = "disco"
genre = fields.Many2one("discografica.genero",ondelete="set
null",string="Estilo",help="Estilo")
price = fields.Float(String="Precio",digits=(4,2))
Esta herencia se usa cuando queremos crear un objeto nuevo a partir de varios objetos ya
existentes, es decir, con herencia múltiple. Para ello se establece la propiedad _name con un
valor no existente y se define la propiedad _inherits como un diccionario con los objetos de
los que queramos heredar.
Extendiendo la vista
Cuando heredamos por prototipo o delegación estamos obligados a crear un nuevo fichero de vista.
En el ejemplo anterior del modelo disco2, podríamos realizar algo como lo que aparece en el
siguiente fichero:
</data>
</openerp>
Es importante prestar atención a que incluso generamos un nuevo menú que enlaza con una nueva
acción. Es decir, no hay reutilización de las vistas, menús, acciones existentes.
Cuando heredamos por extensión si podemos reutilizar las vistas existentes y realizar las
modificaciones oportunas para mostrar nuevos campos u ocultar alguno de los existentes.
y posteriormente sustituimos donde pondríamos el tag <form> por el tag <data>. En el ejemplo
anterior declaramos que tras el campo author se sitúe el campo opinión
• Si hay que reemplazar un campo por otro se realiza igual que el punto anterior, pero
incluimos el nuevo campo antes de finalizar el tag <field>
• Para añadir campos, seleccionamos un campo base que exista utilizando la etiqueta field y
añadiremos el parámetro position con los valores before o after para indicar dónde colocar
los nuevos campos.
• En los casos en que determinar un campo sea difícil porque aparece varias veces, por la
estructura, etc. tendremos que usar xpath de XML para hacer la búsqueda y realizar las
sustituciones oportunas. Por ejemplo, insertar un campo general_rating después del campo
your_rating que estará dentro de un form, que estará dentro de un campo llamado opinion en
cualquier posición del árbol xml
Menús y acciones
Un menú es un elemento que se encarga de enlazar una acción con los clics que realiza el usuario.
Se declara con el tag menuitem y como cualquier otro elemento, utiliza el parámetro id para
identificarlo. Salvo el elemento raíz (menú raíz) que carece de él, todos los menuitem cuentan con
el atributo parent que hacen referencia al nodo inmediatamente superior.
• icon, web-icon, webicon-hover. Definen un fichero para utilizar en los menús. Las rutas son
Acciones
Definen el comportamiento del sistema en respuesta a acciones del usuario: login, botones,
selección de un disco... Podemos encontrar la información completa en la referencia
Los tipos de acciones que podemos usar son:
• Window Action (ir.actions.act_window): las más usadas, permiten lanzar vistas. Los
campos principales a usar son res_model(modelo para presentar las vistas) y views (vistas
existentes). El campo name definirá el título de la vista a la que se llegue mediante esta
acción.
<record model="ir.actions.act_window" id="disco_list_action">
<field name="name"> Discos </field>
<field name="res_model">disco</field>
<field name="view_mode">tree,form</field>
</record>
<field name="name">disco.form</field>
<field name="model">discografica.disco</field>
<field name="arch" type="xml">
<form string="Listado de discos">
<sheet>
<group name="cabecera" string="Título del disco" >
<div>
<label for="name" class="oe_edit_only" />
<h1> <field name="name" placeholder="Título" nolabel="1" /> </h1>
<label for="year" class="oe_edit_only" />
<h2> <field name="year" placeholder="Año" nolabel="1" /> </h2>
</div>
<field name="cover" nolabel="1" widget="image" class="oe_avatar oe_right"
options="{'preview_image':'image_medium','size':[90,90]}"/>
</group>
<notebook>
<page string="Más datos">
<group>
<field name="genre" />
<field name="author" />
</group>
</page>
<page string="Canciones" >
<h1> Lista de canciones del disco </h1>
</page>
<page string="Opiniones" />
</notebook>
</sheet>
</form>
</field>
</record>
La siguiente definición permite que los elementos englobados en el <div> ocupen una única
columna de las utilizadas por el elemento group que los contiene.
<div>
<label for="name" class="oe_edit_only" />
<h1> <field name="name" placeholder="Título" nolabel="1" /> </h1>
El elemento <label for=”campo” nos definirá la etiqueta del campo en caso de que no estuviera
definida (group ya las muestra por defecto) y especificando class=”oe_edit_only” hacemos que
Informes
Los informes en Odoo8 están basados en tres tecnologías Qweb, Bootstrap y wkhtmltopdf. Se
definen en plantillas HTML/Qweb. Qweb es el motor de plantillas basado en xml que incorpora
Odoo por defecto en la versión 8 pero también es posible integrar otros generadores de informes
como Jasperreports o Pentaho. Los informes pueden mostrarse en el propio html -dándole formato
mediante bootstrap- o transformarse a pdf haciendo uso de la librería wkhtmltopdf.
Para realizar el informe de un modelo concreto necesitamos definir dos cosas, la acción
ir.actions.report.xml y la plantilla del informe. También es posible definir el formato de papel a
utilizar. En el caso de que deseemos acceder a más de un modelo para generar el informe, debemos
crear un informe personalizado definiendo una nueva clase que nos de acceso a todos los modelos
deseados.
Definiendo un report
Para definir la acción ir.actions.report.xml podemos utilizar el tag <report> que incorpora los
siguientes parámetros:
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<report
id=”report_disco”
model=”discografica.disco”
string=”Ficha del disco”
name=”discografica.report_discoview”
file=”discografica.report_discoview”
report_type=”qweb-pdf” />
</data>
</openerp>
• id (obligatorio): el identificador del report
En el ejemplo anterior tenemos el aspecto que presenta una plantilla base mínima. La llamada a
external_layout añade la cabecera y pie de página por defecto al informe. El cuerpo del documento
pdf se encuentra en la etiqueta <div class=”page”>. El id de la plantilla <template id=””> debe
coincidir con el especificado en el parámetro name del tag <report name=”” />. Siguiendo la
sintaxis proporcionado por qweb, podemos acceder a todos los campos de los objetos - docs en el
ejemplo- recibidos por la plantilla.
Algunas variables accesibles en los reports son:
• docs: registros del informe actual
Etiquetas Qweb
Como podemos ver en la plantilla base anterior, tenemos una serie de elementos base que se
identifican por la sintaxis <t . Entre estos elementos debemos conocer, al menos, el significado de
los que aparecen en esta plantilla.
El campo t-call se utiliza para llamar a otras plantillas. En este caso se utilizan dos, la primera
report.html_container se utiliza de manera genérica para todos los reports como elemento raíz.
Posteriormente encontramos la llamada a otra plantilla report.external_layout utilizada, como
hemos dicho anteriormente, para añadir la cabecera y pie de página. En cualquier caso, podríamos
definir nuestra propia plantilla y llamarla haciendo uso del template.id establecido.
El siguiente elemento que encontramos es t-foreach...t-as. Tal y como podemos suponer, se utiliza
para recorrer una lista de elementos, de manera similar a como hacemos en python for ele in Lista.
El último tag que encontramos es t-field, utilizado para mostrar el contenido de un campo field del
modelo. Se utiliza conjuntamente como una etiqueta html. Es común encontrar <h1 t-field, <span t-
field, etc.
Por supuesto, Qweb es mucho más potente que estos tres campos. Podemos ver ejemplos y
consultar la referencia completa de Qweb en la documentación de Odoo.
Tal y como se vio en la definición del tag <button> en las vistas tree, el name del botón debe ser el
id de la acción que queramos lanzar. De ahí la sintaxis %(string)d
ATENCIÓN: añadir el botón después de haber creado el report en el sistema. Si lo intentamos hacer
simultáneamente nos dará error. Debe existir REPORT ACTION ID antes de añadirlo.
Internacionalización
Vistas avanzadas
Kanban
Graphs
Calendar
Workflows
Wizards
Webservices