Está en la página 1de 25

Sistemas de Gestión Empresarial

Í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

1 de 25utorial Building a Module


Sistemas de Gestión Empresarial

Desarrollo de módulos para Odoo 8

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

• Vista: fichero XML

• Controlador: Core de Odoo implementado en Python

2 de 25utorial Building a Module


Sistemas de Gestión Empresarial

Además, algunas características a destacar de Odoo son:


1. Está implementado en Python, tanto su core como los módulos que lo extienden.
2. Permite acceso al mismo mediante el protocolo RPC-XML servicios web. Docs
3. Hace uso de ORM para acceder al modelo de datos. No es necesario ni recomendable el
acceso directo a la base de datos. Docs
4. Se pueden insertar y exportar datos mediante ficheros csv, xml...
5. Vistas, informes, formularios, flujos de trabajo...
Además en esta versión 8 incorpora nuevas funcionalidades como la integración con bootstrap para
la interfaz web, incorporación manual de mecanismos extra de seguridad a ciertas partes de una
aplicación, mecanismo de plantillas propio Qweb, ...
Nota.- En la versión 8 existen cambios tanto en la API como en estructuras de los módulos.
Trataremos de reflejar las diferencias para poder ser críticos con la documentación existente en
internet. En su mayoría para versiones anteriores.

Elementos principales de un módulo

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

3 de 25utorial Building a Module


Sistemas de Gestión Empresarial

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 -*-

from . import controllers

from disco import disco

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': """

Ejemplo de uso scaffold Discográfica""",

'description': """

Ejemplo de uso mediante scaffold Discográfica """,

'author': "SGE2015",

'website': "http://www.iesdealquerias.es",

# Categories can be used to filter modules in modules listing

# Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml

# for the full list

'category': 'PrimerosPasos',

'version': '0.1',

# any module necessary for this one to work correctly

'depends': ['base'],

# always loaded

'data': [

# 'security/ir.model.access.csv',

'views/discografica.xml',

],

# only loaded in demonstration mode

'demo': [

'demo.xml',

],

4 de 25utorial Building a Module


Sistemas de Gestión Empresarial

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

• sale.order: orden de venta

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")

5 de 25utorial Building a Module


Sistemas de Gestión Empresarial

El valor por defecto de un campo se pasan como parámetro al crear el field

a_field = fields.Char(default="a value")

Un field también puede llamar a una función para calcular su valor.


a_field = fields.Char(default=compute_default_value)

def compute_default_value(self):

return self.get_value()

Se pueden consultar todas las propiedades de un objeto en la documentación oficial de Odoo,


apartado Model Reference.

Los campos. Fields.


En general, los siguientes parámetros los podremos utilizar al definir cualquier campo:
• string: la etiqueta de campo que verá el usuario. Por defecto, el nombre del campo tipo
Título.
• help: popup que se mostrará al usuario

• readonly: indica si es un campo de solo lectura. Por defecto False.

• required: indica si es un campo obligatorio. Por defecto False.

• default: indica el valor por defecto que debe tener el campo.

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.

6 de 25utorial Building a Module


Sistemas de Gestión Empresarial

Fichero disco.py
# -*- coding: utf-8 -*-

from openerp import models, fields, api

class disco(models.Model):

_name = 'disco.disco'

name = fields.Char(string="Título del disco",help="Título",required=True)

year = fields.Char(string="Año de publicacion",help="Año")

genre = fields.Char(string="Género musical",help="Género")

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...

7 de 25utorial Building a Module


Sistemas de Gestión Empresarial

En el siguiente ejemplo definimos:


• Elementos de menú, menuitem: enlazan con vistas a través de acciones
• Acciones con el atributo model=”ir.actions.act_window”, contiene las acciones posibles
• Vistas con el atributo model=”ir.ui.view”, en este caso de árbol y formulario

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


<openerp>
<data>

<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>

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


<field name="name">disco.form</field>
<field name="model">disco</field>
<field name="arch" type="xml">
<form string="Formulario disco">
<sheet>
<group>
<field name="name" />
<field name="year" />
<field name="genre" />
<field name="author" />
</group>
</sheet>
</form>
</field>
</record>

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


<field name="name">disco.tree</field>
<field name="model">disco</field>
<field name="arch" type="xml">
<tree string="Formulario disco">
<field name="name" />
<field name="year" />
<field name="genre" />
<field name="author" />
</tree>
</field>
</record>

<menuitem id="top_discografica_menu" name="Discográfica" />


<menuitem id="left_discografica_menu" name="Discográfica"
parent="top_discografica_menu" />
<menuitem id="discos_menu" name="Discos" parent="left_discografica_menu"
action="disco_list_action" />

</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

8 de 25utorial Building a Module


Sistemas de Gestión Empresarial

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.

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


<field name="name">disco.tree</field>
<field name="model">disco</field>
<field name="arch" type="xml">
<tree string="Formulario disco">
<field name="name" />
<field name="year" />
<field name="genre" />
<field name="author" />
</tree>
</field>
</record>

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.

4. newline: para especificar un salto de fila en un tag group


5. separator: línea para separar dos secciones
6. sheet: usada dentro de un formulario (similar a page)
7. header: usada normalmente para mostrar los botones de workflow y los widgets de estado.

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

9 de 25utorial Building a Module


Sistemas de Gestión Empresarial

• 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.

2. field ( opción d) únicamente en vistas Tree, a partir de h) únicamente en vistas Form)


a) name: nombre del campo en el modelo
b) string: título del campo a mostrar. Por defecto toma el definido en el modelo.
c) invisible: campos que se almacenan pero no se muestran en la columna.
d) sum,avg ( en vistas tree): muestra la función agregada correspondiente al final de la
columna. Solo se calcula de los campos mostrados. La agregación debe corresponderse
con el grupo correspondiente.
e) groups: lista los grupos que deberían poder ver el campo
f) widget: vista alternativa del campo. Los posibles valores son:
• progressbar: para los campos float
• many2onebutton: reemplaza el campo por un check si el campo se completa.
• handle: para sequences. Muestra un icono en lugar del valor.
• Many2many: ver la sección de campos relacionados para ver posibles valores.
g) attrs: paso de valores para ciertos campos
h) class:
• oe_inline: para que el objeto que le siga se muestre en la misma línea.
• oe_left, oe_right: para posicionar a la izquierda o derecha.
• oe_read_only, oe_edit_only: para que se muestren como solo lectura o solo en modo
edición.
• oe_no_button: para no mostrar el botón de navegación en campos many2one
• oe_avatar: en campos imagen, los muestra como 90x90
i) onchange: deprecated. Usar el método openerp.api.onchange() en el modelo
j) options: objeto JSON pasando opciones
k) domain: únicamente para campos relacionales. Filtra por un campo.
l) context: únicamente para campos relacionales.
m)readonly
n) required
o) nolabel
p) placeholder: mensaje a mostrar en campos vacíos.
q) mode: por defecto tree. En campos relacionales especifica el tipo de vista.
r) help: mensaje del popup
s) filename: para campos binarios contiene el nombre del fichero.
t) password

10 de 25utorial Building a Module


Sistemas de Gestión Empresarial

Las vistas de Odoo generalmente se componen de tres elementos:


(ver en la documentación ejemplos detallados)

1. Una barra de estado


2. Una hoja, el propio formularios
3. Una parte final con historial y comentarios

Esto equivale en el fichero xml a:

<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>

Definición del modelo: estableciendo campos/Field


Los fields los podemos clasificar en tres categorias:
• Campos básicos: Char, Boolean, Integer, Float, Text, Selection, Html, Date, Datetime
• Campos calculados
• Campos relacionados

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:

• compute: nombre del método que calcula el campo

• inverse: nombre del método que invierte el valor del campo

• search: nombre del método que implementa la búsqueda en el campo

• store: booleano que indica si debe almacenarse en BBDD

11 de 25utorial Building a Module


Sistemas de Gestión Empresarial

upper = fields.Char(compute='_compute_upper', inverse='_inverse_upper', search='_search_upper')

@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

def _search_upper(self, operator, value):


if operator == 'like':
operator = 'ilike'

Campos básico: selection


Probablemente, el módulo discográfica que vemos en los ejemplos, necesite unas mejoras para
hacerlo más funcional. En primer lugar, habrá campos a los que deseemos controlar los posibles
valores de entrada. Esta acción la podemos llevar a cabo de dos maneras, mediante campos
selection y mediante many2one.

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.

<field name="author" widget="selection" />

Tras realizar ambos cambios ya podemos tener una aproximación mejor al resultado que buscamos.

12 de 25utorial Building a Module


Sistemas de Gestión Empresarial

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:

• Many2one: el valor de este campo será un recordset de cero o un registro


Esta relación se daría por ejemplo en el caso que nos ocupa entre el modelo disco y un
comodel autor o género
Many2one(comodel_name, string, **kwargs)
• comodel_name: nombre del modelo destino (obligatorio)
• domain: filtro opcional para especificar los posibles valores. Referencia

domain=[('instructor', '=', True)]


domain=[('name','ilike','kIs')]

13 de 25utorial Building a Module


Sistemas de Gestión Empresarial

• context: contexto. Diccionario de keys-values


• ondelete: 'set null', 'restrict' o 'cascade'
• auto_join: por defecto False
• delegate: True para hacer accesibles los campos del modelo destino desde el modelo
actual.

genre_id = fields.Many2one("discografica.genero", ondelete="set null",


string="Estilo", help="Estilo")
Prestad atención al _id que pretende resaltar que la relación se realizará mediante el campo id.
Cuando utilicemos el widget=”selection” con este tipo de campos se mostrará un listado de
identificadores del modelo (ids) que cumplan el domain establecido. Si deseamos ser más
descriptivos y mostrar el contenido de otro campo disponemos de dos soluciones:
a) Establecer un campo llamado name en el comodel. Este es el que se mostrará por defecto.
En el ejemplo sería en discografica.genero
b) Sobreescribir el método name_get encargado de mostrar este tipo de campos. Para ello
podemos hacer algo como:
def name_get(self, cr, uid, ids, context=None):
if context is None:
context = {}
if isinstance(ids, (int, long)):
ids = [ids]
res = []
for record in self.browse(cr, uid, ids, context=context):
name = record.name + “-SGE”
res.append((record.id, name))
return res
Y de este modo conseguiremos el siguiente cambio:

• 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

• Many2many: el valor de este campo es un recordset


Many2many(comodel_name, relation, column1, column2, string, **kwargs)
• comodel_name: nombre del modelo destino (obligatorio)
• relation: nombre opcional de la tabla que almacena la relación en BBDD
• column1: nombre opcional de la columna que se refiere a estos registros en la tabla
relation
• column2: nombre opcional de la columna que se refiere a aquellos registros en la tabla
relation
Ver ejemplo en el t: Excercise many2many relations

14 de 25utorial Building a Module


Sistemas de Gestión Empresarial

Por ejemplo, en un modelo de autores/artistas/bandas podríamos tener el campo:

genre_ids = fields.Many2many("discografica.genero",
ondelete="cascade",required=True,string=”Estilos musicales”

Al definir el campo en la vista, disponemos de varias opciones de visualización mediante el


parámetro widget: many2many (por defecto), many2many_tags (tipo facebook),
many2many_checkboxes, many2many_kanban, x2many_counter, many2many_binary.
Más info aquí

Herencia

Extendiendo el modelo

Ya conocemos como se implementa la herencia en Python. Aquí trataremos como se trata la


herencia en Odoo.

15 de 25utorial Building a Module


Sistemas de Gestión Empresarial

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 -*-

from openerp import models, fields, api

class discoExtendido(models.Model):

_name = 'disco'

_inherit = "disco"

genre = fields.Many2one("discografica.genero",ondelete="set
null",string="Estilo",help="Estilo")

opinion = fields.Char(String="Opinión", help="Opinión")

◦ 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 -*-

from openerp import models, fields, api

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))

• Herencia por delegación

Esta herencia se usa cuando queremos crear un objeto nuevo a partir de varios objetos ya

16 de 25utorial Building a Module


Sistemas de Gestión Empresarial

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:

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


<openerp>
<data>
<record model="ir.actions.act_window" id="disco2_list_action">
<field name="name"> Discos </field>
<field name="res_model">discografica.disco2</field>
<field name="view_mode">tree,form</field>
</record>

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


<field name="name">disco2.form</field>
<field name="model">discografica.disco2</field>
<field name="arch" type="xml">
<form string="Formulario 2disco">
<group>
<field name="name" />
<field name="year" />
<field name="genre" />
<field name="author" widget="selection" />
<field name="price" />
</group>
</form>
</field>
</record>

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


<field name="name">disco2.tree</field>
<field name="model">discografica.disco2</field>
<field name="arch" type="xml">
<tree string="Formulario disco">
<field name="name" />
<field name="year" />
<field name="genre" />
<field name="author" />
<field name="price" />
</tree>
</field>
</record>
<!-- window action -->
<menuitem id="discos2_menu" name="Discos2"
parent="discografica_menu" action="disco2_list_action" />

</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.

17 de 25utorial Building a Module


Sistemas de Gestión Empresarial

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.

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


<openerp>
<data>

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


<field name="name"> Discos </field>
<field name="res_model">disco</field>
<field name="view_mode">tree,form</field>
</record>

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


<field name="name">disco3.form</field>
<field name="model">disco</field>
<field name="inherit_id" ref="discografica.disco_form_view" />
<field name="arch" type="xml">
<data>
<field name="author" widget="selection" position="after" >
<field name="opinion" />
</field>
</data>
</field>
</record>

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


<field name="name">disco3.tree</field>
<field name="model">disco</field>
<field name="arch" type="xml">
<tree string="Formulario disco">
<field name="name" />
<field name="year" />
<field name="genre" />
<field name="author" />
</tree>
</field>
</record>
<!-- window action -->
<menuitem id="discos3_menu" name="Discos3" parent="discografica.discografica_menu"
action="disco3_list_action" />
</data>
</openerp>

De nuevo es importante prestar atención al código resaltado en negrita en la definición de la vista


formulario. En este caso, indicamos un campo para definir la vista como una extensión (atención a
la notación para referirnos al identificador de la vista padre. Namespace completo)

<field name="inherit_id" ref="discografica.disco_form_view" />

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

<field name="author" widget="selection" position="after" >


<field name="opinion" />
</field>

Posibles situaciones a tratar:

• Si hay que eliminar un campo añadiremos una etiqueta

18 de 25utorial Building a Module


Sistemas de Gestión Empresarial

<field name=”nombre-campo-a-eliminar-vista-padre” position=”replace” />

• 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>

<field name=”nombre-campo-a-eliminar-vista-padre” position=”replace” >


<field name=”nuevo-campo” />
</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.

<field name="campo-existente" position="before | after" >


<field name="nuevo-campo1" />
<field name="nuevo-campo2" />
</field>

• 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

<xpath expr=”//field[@name='opinion']/form/field[@name='your_rating']“ position=”after”>


<field name="nuevo-campo1" />
</xpath>

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.

<menuitem id="discos3_menu" name="Discos3"


parent="discografica.discografica_menu" action="disco3_list_action" />

Además pueden/deben contener las etiquetas:


• name=”valor”. El nombre del menú

• action=”id_action”. Identificador de la acción a lanzar.

• icon, web-icon, webicon-hover. Definen un fichero para utilizar en los menús. Las rutas son

19 de 25utorial Building a Module


Sistemas de Gestión Empresarial

relativas a la raíz de la instalación del módulo


• groups. Campo que puede contener un conjunto de grupos de seguridad del sistema
separados por pcomas indicando aquellos que tienen acceso ***v8
• sequence=”N”. Se utiliza para organizar los elementos del menú. Si no existen se organizan
por orden de aparición.

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>

• URL actions (ir.actions.act_url): abre una URL

• Server actions (ir.actions.server): ejecuta código complejo en el servidor desde cualquier


lugar
• Report actions (ir.actions.report.xml): lanza la ejecución de un informe

• Client actions (ir.actions.client): ejecuta una acción completamente en el cliente

Personalización de las vistas


Es importante destacar que podemos combinar elementos del lenguaje html en la definición de las
vistas. De este modo, elementos como <div>, <h1>,<h2>...pueden ser incluidos para dar agrupar
elementos y/o dar formato.
Ya hemos ido viendo como parámetros del elemento <field> como widget nos ayudan a mejorar la
apariencia de nuestras vistas. Veamos ahora un ejemplo del uso de otros elementos.
<record model="ir.ui.view" id="disco_form_view">

20 de 25utorial Building a Module


Sistemas de Gestión Empresarial

<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>

Definición del grupo con los parámetros name y string


conseguimos:
• name: nos permitirá acceder a los elementos del
grupo con mayor facilidad si decidimos extender la
vista.
• String: permitirá establecer una etiqueta común a
todos los elementos del grupo
<group name="cabecera" string="Título del disco" >

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>

<label for="year" class="oe_edit_only" />


<h2> <field name="year" placeholder="Año" nolabel="1" /> </h2>
</div>

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

21 de 25utorial Building a Module


Sistemas de Gestión Empresarial

únicamente se muestre la etiqueta al editar el elemento.


Por último, destacar los parámetros placeholder y nolabel del
elemento field. El primero mostrará un texto dentro del campo
cuando estemos en modo edición. El segundo forzará que no
se muestre la etiqueta del campo.

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

• name (obligatorio): será la referencia a buscar al definir la plantilla

• model (obligatorio): el modelo del que se quiere realizar el report

• report_type (obligatorio): tipo de informe a generar, qweb-html o qweb-pdf.

• file: nombre estático del fichero pdf a generar

22 de 25utorial Building a Module


Sistemas de Gestión Empresarial

Definiendo una plantilla


<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id=”report_disco_view” >
<t t-call=”report.html_container”>
<t t-foreach=”docs” t-as=”doc”>
<t t-call=”report.external_layout”>
<div class=”page”>
<h2 t-field=”name” />
<h3 t-field=”year” />
</div>
</t>
</t>
</t>
</template >
</data>
</openerp>

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

• docs_ids: lista de identificadores de los registros

• time: referencia a time de Python

• translate_doc: función para traducir el informe

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.

23 de 25utorial Building a Module


Sistemas de Gestión Empresarial

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.

Ejemplo: añadir un botón para lanzar un report


En el <header> de la vista deseada añadir:
<header>
<button name="%(REPORT ACTION ID)d" string="YOUR STRING" type="action" icon="gtk-print"/>
</header>

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.

ATENCIÓN: si detectamos que el report en formato qweb-pdf tarda demasiado en generarse, es


porque hay un parámetro del sistema que se ha configurado de manera errónea. Para modificarlo
debemos realizar los siguientes pasos:
1. Menú Configuración
2. Parámetros

3. Modificar web.base.url y establecer el valor 127.0.0.1:8069 (siempre se genera en el


servidor).

24 de 25utorial Building a Module


Sistemas de Gestión Empresarial

Internacionalización

Vistas avanzadas
Kanban

Graphs

Calendar
Workflows

Wizards

Webservices

25 de 25utorial Building a Module

También podría gustarte