Sei sulla pagina 1di 34

Creación de un módulo

OpenERP
Índice de contenidos
1. Introducción......................................................................................................................................3
2. Modelo, Vista, Controlador (MVC)..................................................................................................4
2.1. Modelo Vista Controlador en OpenERP...................................................................................5
3. Los módulos.....................................................................................................................................6
3.1. Módulo ejemplo academy.........................................................................................................6
4. Descripción de módulo: Fichero __openerp__.py............................................................................8
4.1. Módulo ejemplo academy.........................................................................................................8
5. Programación del modelo en Python..............................................................................................10
5.1. Definición de las tablas...........................................................................................................10
5.1.1. Definición de las columnas..............................................................................................10
a) Campos simples................................................................................................................10
b) Campos relacionales.........................................................................................................11
c) Campos funcionales.........................................................................................................12
5.1.2. Módulo ejemplo academy................................................................................................12
6. Programación de los menús, acciones y vistas: Archivos XML.....................................................15
6.1. Los menús................................................................................................................................16
6.1.1. Módulo ejemplo academy................................................................................................17
6.2. Las acciones............................................................................................................................18
6.2.1. Acciones window (apertura de vistas).............................................................................18
6.2.2. El dominio de la acción. Condiciones.............................................................................21
6.2.3. Módulo ejemplo academy...............................................................................................22
6.3. Las vistas.................................................................................................................................23
6.3.1. Vistas de formulario........................................................................................................25
a) Módulo ejemplo academy................................................................................................28
6.3.2. Vistas de lista o árbol......................................................................................................31
a) Módulo ejemplo academy.................................................................................................31
7. Anexo 1...........................................................................................................................................34
1.  Introducción
OpenERP  es un programa Cliente/Servidor basado en Python para la planificación de recursos 
empresariales   (ERP).   Consta   de   un   cliente  OpenERP­Client  y   un   servidor  OpenERP­Server 
mientras   que   la   persistencia   de   datos   está   proporcionada   por   la   base   de   datos   relacional 
PostgreSQL.

Figura 1.1: Estructura de OpenERP 

OpenERP utiliza el protocolo XML­RPC o NET­RPC para la comunicación entre cliente y servidor 
También dispone de un cliente web para generar la interfaz gráfica del usuario como una página 
web y así poder usar OpenERP desde un navegador web (figura 1.1).

Una vez instalado, OpenERP tiene una estructura modular permitiendo añadir módulos según vaya 
siendo necesario.

OpenERP   funciona   sobre   un   entorno   de   desarrollo   o   framework   llamado   OpenObject.   Este 


framework permite el desarrollo rápido de aplicaciones (RAD ­ Rapid Application development)

OpenObject es un entorno de desarrollo rápido de aplicaciones de gestión orientado a objetos en 
Python usando base de datos PostgreSQL. Sus componentes son:

● ORM (Object Relational Mapping): Mapeo de bases de datos relacionales a objetos Python

● Arquitectura MVC (Modelo Vista Controlador)

● Sistema de flujos de trabajo o workflows

● Diseñador de informes o reports (OpenOffice, JasperReports, ...)

● Herramientas BI (Business Intelligence) y OLAP cube engine

● Clientes Gtk, Koo (KDE) y Web
2.  Modelo, Vista, Controlador (MVC)
OpenERP utiliza el patrón de arquitectura de software Modelo Vista Controlador (figura 2.1).

Una definición de este patrón podría ser la que ofrece Wikipedia: Modelo Vista Controlador (MVC) 
es un patrón de arquitectura de software que separa en tres componentes distintos:

● Los datos de la aplicación (modelo)

● La interfaz de usuario (vista)

● La lógica de control (controlador)

El patrón MVC se ve frecuentemente en aplicaciones web donde:

● La vista es la página HTML y el código que provee de datos dinámicos a la página.

● El modelo es el Sistema de Gestión de Base de Datos y la lógica de negocio.

● El controlador es el responsable de recibir los eventos de entrada desde la vista.

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 manipulación de datos, y que los datos pueden ser reorganizados sin 
cambiar  la  interfaz  de usuario.  El  modelo­vista­controlador  resuelve este  problema  mediante  la 
disociación del acceso a datos y la lógica de negocio de la presentación de los datos y la interacción 
del usuario, mediante la introducción de un componente intermedio: el controlador.

Figura 2.1: Modelo Vista 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 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 diseño son las siguientes:
• Acceso de modelo a vista: El modelo envía la notificación 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 operación. 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 mínimas ya que el controlador pueda ser sustituido en cualquier momento.

2.1.  Modelo Vista Controlador en OpenERP
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 creación/actualización automática de las tablas 
y acceder a las tablas sin usar SQL.
• Vista (view): Listas, formularios, calendarios, gráficos, ... definidas en archivos XML. En estos 
archivos también se definen menús, acciones, informes, asistentes, ...
• Controlador  (controller): Métodos Python definidos dentro de los objetos de OpenERP que 
proporcionan la lógica: Validación de datos, cálculos, ...
3.  Los módulos
El uso de los módulos es la manera de ampliar la funcionalidad OpenERP. Una instalación de 
OpenERP  se compone de un núcleo y de varios  módulos  dependiendo de las  necesidades, por 
ejemplo:
• base: El módulo básico compuesto por los objetos básicos como compañías, monedas, usuarios, 
empresas, direcciones, ... (res.company, res.currency, res.user, res.partner, res.partner.address, ...
). Este módulo se instala siempre. 
• account: Gestión contable / financiera / analítica.
• crm: Gestión de la relación con los clientes/proveedores. 
• product: Gestión de productos, tarifas.
• sale: Gestión de ventas.
• purchase: Gestión de compras.
• stock: Gestión de logística, almacenes.
• mrp: Fabricación y planificación de recursos. 
Los nuevos módulos pueden programarse fácilmente y requieren un poco de práctica en XML y 
Python.

Todos   los   módulos   están   ubicados   en   la   carpeta  bin/addons  del   directorio   de   instalación   del 
servidor OpenERP.

Los pasos básicos para generar un módulo son:
• Crear un subdirectorio dentro de la carpeta bin/addons del directorio de instalación del servidor 
OpenERP. 
• Crear un archivo de inicio del módulo de Python: __init__.py (que importa los otros archivos .py 
que tenga el módulo).
• Crear un archivo con la descripción del módulo de OpenERP: __openerp__.py (Nota: Hasta la 
versión 5.0 se denominaba __terp__.py).
• Crear los archivos Python que contendrán los objetos (clases con atributos y métodos). Aquí es 
donde se define el modelo y el controlador. 
• Crear los archivos XML para la definición de datos (vistas, acciones, menús, 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, ...)
• Opcionalmente, aunque recomendable, poner las traducciones en una carpeta llamada i18n i las 
reglas de seguridad en una carpeta llamada security dentro del mismo módulo.
En todos los ficheros acostumbra a aparecer una cabecera que es un comentario Python referido a la 
licencia   del   módulo   y   el   copyright   del   autor/es,   esto   se   puede   copiar   de   cualquier   fichero   de 
cualquier módulo y adaptarlo a nuestras necesidades.

3.1.  Módulo ejemplo academy
La carpeta de nuestro módulo academy tendrá una estructura similar a esta:
• academy
– __openerp__.py
– __init__.py
– academy.py
– academy_view.xml
– security
∙ ir.model.access.csv
– i18n
∙ academy.pot
∙ es.po
∙ ca.po

Si dentro de nuestro módulo academy creamos un archivo "academy.py" que contiene la definición 
de nuestros objetos, tenemos que incluir la siguiente línea en el archivo __init__.py:
import academy
4.  Descripción de módulo: Fichero __openerp__.py
Cualquier módulo que creemos debe contener un un archivo __openerp__.py (hasta la versión 5.0 
se denominaba __tinyerp__.py) ubicado en la raíz de nuestro módulo . Este archivo, que debe tener 
formato de un diccionario de Python, es responsable de determinar, entre otras cosas:
• El nombre, descripción, autores, licencia, versión, categoría, ... del módulo.
• Los archivos XML que serán analizados durante el arranque del servidor OpenERP. 
• Las dependencias con otros módulos.
Este archivo debe contener los siguientes valores dentro de un diccionario Python  (estructura de 
datos cerrada con llaves { }):
• name: Nombre del módulo.
• version: Versión del módulo
• description: Descripción del módulo.
• author: Autor del módulo
• website: Sitio web del modulo
• license: Licencia del módulo (por defecto GPL­3)
• category:   Categoría   a   la   que   pertenece   el   módulo.   Texto   separado   por   barras   /   con   la   ruta 
jerárquica de las categorías. 
• depends: Lista de módulos de los que depende este módulo. Muchas veces se incluye el módulo 
base en las dependencias ya que algunos datos de este módulo son necesarios para las vistas, 
informes, ...
• init_xml: Lista de archivos XML que se cargaran al iniciar el servidor OpenERP con la opción 
"­init=modulo". Las rutas de los archivos debe ser relativas al directorio donde está el módulo.
• update_xml:  Lista de archivos XML que se cargaran al iniciar el servidor OpenERP con la 
opción "­update=modulo". Las rutas de los archivos debe ser relativas al directorio donde está 
el módulo.
• demo_xml: Lista de archivos XML que se cargaran si se ha instalado el servidor OpenERP con 
datos de demostración o ejemplo. Las rutas de los archivos debe ser relativas al directorio donde 
está el módulo.
• installable: Acepta valores True o False y determina si el módulo es instalable o no.
• active: acepta valores True o False y determina los si el módulo será instalado cuando se crea la 
base de datos (por defecto False)
4.1.  Módulo ejemplo academy
Este es un ejemplo del archivo __openerp__.py para el módulo academy:
{
"name" : "academy",
"version" : "0.1",
"author" : "zikzakmeida",
"website" : "http://zikzakmedia.com",
"category" : "Generic Modules",
"description": """Academy module description""",
"depends" : ['base'],
"init_xml" : [ ],
"demo_xml" : [ ],
"update_xml" : ['academy_view.xml'],
"installable": True
}

Los archivos que se deben incluir en el parámetro init_xml son los que se refieren a las definiciones 
del flujo de trabajo, para cargar datos en la instalación del módulo.

Los archivos que se incluyen en  update_xml  conciernen a vistas,  acciones, menús,  informes y 


asistentes.

Los archivos que se incluyen en demo_xml son para cargar los datos de demostración.

Observar que:
• Todos los datos del archivo  __openerp__.py  están dentro de un diccionario Python dentro de 
llaves { }.
• Los archivos XML de los parámetros  init_xml,  update_xml  o  demo_xml  están dentro de una 
lista de Python dentro de corchetes [].
5.  Programación del modelo en Python
Después de describir el módulo mediante el fichero __openerp__.py vamos a definir los modelos u 
objetos en OpenERP, los cuales crearán de forma automática las tablas y campos en la base de datos 
PostgreSQL.

5.1.  Definición de las tablas
En OpenERP, las tablas de PostgreSQL se crean mediante una serie de atributos predefinidos  en 
clases escritas en codigo Python heredadas de la clase osv.osv.  Aunque estrictamente hablando sean 
clases de Python, se les acostumbra a llamar objectos de OpenObject. Hay dos atributos obligatorios 
(que siempre se deben definir) y el resto son opcionales. Los obligatorio son:
• _name: Contiene el nombre de la tabla (normalmente incluye al principio el nombre del módulo 
separado   por   un   punto).   Este   nombre   se   usará   por   defecto   como   nombre   de  la   tabla   de 
PostgreSQL, cambiando los puntos por guiones bajos.
• _columns: Contiene un diccionario Python con la declaración de las columnas de la tabla.
Entre los atributos opcionales podemos destacar:
• _defaults: Sirve para determinar valores por defecto en determinadas columnas.
• _inherit: Sirve para heredar de otra clase existente en OpenERP.
• _constraints: Se utiliza para definir restricciones a nivel de código Python en la gestión de las 
tablas a través de OpenERP.
• _slq_constraints:   Se   utiliza   para   definir   restricciones   a   nivel   de   código   SQL   en   las   tablas 
gestionadas por PostgreSQL.
• _table: Nombre de la tabla SQL si queremos que sea distinto de _name (se sustituye los puntos 
por guiones bajos).
5.1.1.  Definición de las columnas

Dentro del atributo  _columns  de la clase que define la tabla, deben definirse las columnas de la 


misma. Para ello, el componente de OpenObject, Object Service (OSV), proporciona una serie de 
campos (fields) predefinidos.

La sintaxis general para definir una columna es la siguiente:
'nombre_campo':fields.tipo_de_campo('String',['parametro_opcional',...])

Donde tipo_de_campo es el tipo de campo de la tabla relacional PostgreSQL. Existen los siguientes:
a)  Campos simples
• boolean: Sólo puede tomar los valores verdadero y falso (True o False).
• integer: Número entero.
• date: Fecha.
• datetime: Fecha y hora.
• time: Hora.
• char: Cadena de caracteres.
• text: Texto.
• float: Número real.
• selection: Toma uno de una serie de valores predefinidos.
• reference: Campo con una relación dinámica hacia otro objeto de OpenERP seleccionable dentro 
de una lista de posibles objetos (se puede editar desde el menú Administración/Objetos de bajo  
nivel/Solicitudes/Enlaces acceptados en solicitudes).
• binary: Campo para almacenar un archivo o contenido binario.
Todos estos campos tienen una serie de atributos comunes que se pasan como parámetros de la 
función y entre los que cabe destacar:
'string',['parametro_opcional',...]
• string: Es una cadena de texto que pone nombre a la etiqueta del campo. Este atributo es el único 
obligatorio.
• required: Si el campo es obligatorio, True. Por defecto es False.
• readonly: Si el campo es de sólo lectura, True. Por defecto es False.
• select: Si se quiere que de dicho campo se genere un índice (de PostgreSQL) para optimizar las 
búsquedas de las vistas, poner a  1  (búsqueda simple). Para búsqueda avanzada poner a  2. Por 
defecto es 0.
• help: Mensaje de ayuda (el que aparece en el interrogante verde al lado de la etiqueta del campo).
Además, algunos tipos de campo tienen atributos específicos:
• translate: que es True si el valor del campo puede ser traducido por los usuarios. Disponible para 
los campos char y text.
• size: que es el máximo número de caracteres para los campos de tipo char.
• digits: proporciona la precisión y la escala que se el desea asignar mediante una tupla en los 
campos de tipo float.
• values: campo que permite seleccionar entre un conjunto de valores predefinidos en los campos 
de tipo selection. Este atributo se coloca antes que el atributo string, y se define mediante una 
lista de valores o una función que devuelve una lista de valores. La lista debe estar compuesta por 
tuplas que contienen una clave y su etiqueta.
[('key1','Label1'),('key2','Labe2'),...],'String',...
b)  Campos relacionales
Según la wikipedia, las relaciones “permiten establecer interconexiones (relaciones) entre los datos 
(que están guardados en tablas), y a través de dichas conexiones relacionar los datos de ambas 
tablas”. Se pueden dar tres tipos distintos de relaciones entre los objetos de OpenERP que se pueden 
plasmar mediante tres tipos diferentes de campos:
• many2one: Un campo many2one expresa una relación hacia un objeto padre (de muchos a uno), 
usando una llave foránea (foreing key).
'other.object.name', 'Field Name', ...
• one2many: Un campo one2many expresa una relación de un objeto hacia muchos objetos. Es la 
relación contraria y/o complementaria a many2one.
'other.object.name', 'field_id', 'Field Name', ...
• many2many: Un campo many2many representa una relación de muchos a muchos entre dos 
objetos.
'other.object.name','relationship_table','field_id','other_object_field_id',...

Donde:
• other.object.name: Es el nombre del objeto de destino (con el que guarda relación). Común a 
todos los tipos. Obligatorio.
• Field Name: es el nombre del campo. Común y obligatorio en todos los tipos de campos.
• field_id: es el campo identificador de la relación de la propia tabla. Común a los tipos one2many 
y many2many. Obligatorio.
• other_object_field_id: es el campo identificador de la relación de la otra tabla. Solo para el tipo 
many2many. Obligatorio.
• relationship_table: es la tabla que establece la relación en las relaciones de tipo  many2many. 
Obligatorio.
• optionalParameters: para el tipo many2one. Pueden ser:
– ondelete: Indica qué debería ocurrir cuando el recurso al que este campo señala es eliminado.
Valor predefinido: cascade, elimina los recursos dependientes
Valor por defecto: null, no elimina los recursos sino que los establece a un valor nulo.
– required: True por defecto.
– readonly: True por defecto.
– select: True ­ (crea un índice en el campo de la clave foránea)
• optionalParameters: para el tipo one2many. Pueden ser:
– invisible: True/False
– readonly: True/False
Por convenio los nombres de los campos many2one terminan en _id y los nombre de los campos 
one2many y many2many terminan en _ids. Así el programador, con sólo verlos, sabe a que tipo de 
campo se refiere.
c)  Campos funcionales
• function: Los campos funcionales simulan campos reales, pero se calculan mediante una función 
Python   en  lugar  de   almacenarse   en  las   base   de  datos   PosgreSQL.   En   casos   especiales,   para 
aumentar la velocidad de consulta de OpenERP y facilitar las búsquedas, los campos funcionales 
también pueden ser guardados en la base de datos aunque siempre son calculados/actualizados 
por una o varias funciones y no por los usuarios.
• related: Permite visualizar un campo de un objeto relacionado en el propio objeto. Equivalente a 
navegar por campos encadenados. Por ejemplo, el campo Ciudad de las Empresas es un campo 
relacionado pues en realidad se usa el campo Ciudad de las Direcciones/Contactos de la empresa. 
• property:   Campos   dinámicos   con   derechos   de   acceso   específicos.   Por   ejemplo,   los   campos 
Cuenta a cobrar y Cuenta a pagar de la ficha de empresa. Según la compañía a la que pertenezca 
el usuario, el valor de estos campos varía. Los valores de los campos de tipo property se pueden 
consultar en el menú Administración/Configuración/Propiedades.
5.1.2.  Módulo ejemplo academy.

Para   crear   el   objeto  classroom  del   módulo  academy.py  que   permita   gestionar   las   aulas   de   la 
academia se introduce el siguiente código:
class academy_classroom(osv.osv):
"""Academy Classrooms"""
_name = 'academy.classroom'
_columns = {
'name': fields.char('Classroom',size=35, help='Name of the classroom'),
'address_ids': fields.many2one('res.partner.address','Address',
help='Address of the classroom'),
'capacity': fields.integer('Capacity', help='Capacity of the
classroom'),
'state': fields.selection([('usable','Usable'),('in works','In Works')
],'State', help='State of the classroom'),
'active': fields.boolean('Active'),
}
_defaults = {
'state': lambda *a: 'usable',
'active': lambda *a: 1,
}
academy_classroom()

Con este código se creará una tabla de PostgreSQL que utilizará el nombre academy_classroom, 
con los siguientes campos:
• name: Un campo que contiene una cadena de caracteres con el nombre de la aula.
• address_id: Un enlace con la clase res.partner.address en una relación varios a uno (many2one). 
Permite guardar la dirección física donde se ubica la aula.
• capacity: Un campo que contiene un número entero con la capacidad de la aula.
• state: Un campo de selección con el estado en que se encuentra la aula.
• active: Un campo booleano. Determina si el campo es visible “True” o no “False”.
Algunos campos  necesitan que se modifiquen sus valores por defecto. Esto se consigue con el 
atributo _defaults. En este ejemplo, se puede comprobar que los valores por defecto de los campos 
state y active, se han establecido a los valores por defecto usable y 1 (True) respectivamente.

Los modelos/tablas course y course_category para gestionar cursos y categorías de cursos se crean 
de forma análoga. Modelo curso:
class academy_course(osv.osv):
"""Courses taught by academy"""
_name = 'academy.course'
_columns = {
'name': fields.char('Course',size=32, help='Name of the course'),
'category_id': fields.many2one('academy.course.category','Category',
help='Category of the course'),
'classroom_id': fields.many2one('academy.classroom','Classroom',
help='Class in which the course is taught'),
'dificulty': fields.selection([('low','Low'),('medium','Medium'),
('high','High')],'Dificulty', help='Dificulty of the course'),
'hours': fields.float('Studies Hours', help='Hours of the course'),
'state': fields.selection([('draft','Draft'),('testing','Testing'),
('stable','Stable')],'State'),
'description': fields.text('Description', help='Description of the
course'),
}
_defaults = {
'state': lambda *a: 'draft',
'dificulty': lambda *a: 'low',
}
academy_course()

Observar que en este diseño del módulo  academy  se ha supuesto que un curso se imparte en una 


aula y pertenece a una categoría.

Modelo categoría de cursos:
class academy_course_category(osv.osv):
"""Courses Categories"""
_name = 'academy.course.category'
_columns = {
'name': fields.char('Category',size=16, help='Name of the category'),
'parent_id': fields.many2one('academy.course.category','Parent
Category', help='Name of the parent category'),
}
academy_course_category()

Observar que el campo parent_id es un campo many2one que se apunta a si mismo y permite crear 
una   estructura   jerárquica   de   categorías,   donde   cada   categoría   apunta   a   su   padre   excepto   las 
categorías principales.
6.  Programación de los menús, acciones y vistas: Archivos XML
Dado que todos los datos del programa están almacenados en objetos, ¿cómo se muestran dichos 
objetos al usuario? Primero de todo, nótese que cada tipo de recurso utiliza su propia interfaz. Por 
ejemplo, el formulario para modificar los datos de una empresa no es el mismo que el utilizado para 
modificar una factura. En consecuencia, debe tenerse en cuenta que las interfaces son dinámicas. 
Esto significa que no se describen de forma estática por un código, sino que son construidas de 
forma dinámica por la descripción XML de la pantalla del cliente. En adelante, se llamará a estas 
pantallas   de   descripción   vistas   (listas,   formularios,   calendarios,   gráficos,   ...).  Una  característica 
notable de dichas vistas es que pueden editarse en cualquier momento (incluso durante la ejecución 
del programa) desde el menú Administración / Personalización / Interfaz de usuario / Vistas. Una 
vez modificada la vista, simplemente se necesita cerrar la pestaña correspondiente a dicha vista y 
reabrirla para que aparezcan los cambios.

Por lo tanto, una vez creado el primer objeto para el módulo, lo siguiente que se debe hacer es crear 
el fichero encargado de crear los menús, acciones y vistas para poder interactuar con el objeto. La 
estructura general del dicho fichero es la siguiente:
<?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>

Las vistas describen como se muestra cada objeto (tipo de recurso). Precisando más, para cada 
objeto, se pueden definir una o varias vistas que describen qué campos deben mostrarse, y cómo 
debe hacerse.

Hay varios tipos de vistas:
• Formulario
• Lista
• Árbol
• Gráfico
• Calendario
• Diagramas de Gantt
• Búsqueda
Para crear las vistas del módulo se debe crear/editar el fichero nombre_modulo_view.xml. En el 
presente ejemplo, sería:
academy_view.xml

Pero para acceder a dichas vistas, deben definirse previamente los menús, así como las acciones 
asociadas a los mismos.

6.1.  Los menús.
La figura 6.1 muestra un ejemplo de los menús del módulo empresa, que se instala por defecto en 
todos los servidores OpenERP.

Figura 6.1: Ejemplo de menús.

Esta es la plantilla para crear un menú:
<menuitem id="menuitem_id"
name="Position/Of/The/Menu/Item/In/The/Tree"
action="action_id"
icon="NAME_FROM_LIST"
parent="menuitem_id"
groups="groupname"
sequence="<integer>"
/>

Donde:
• menuitem_id: Especifica el identificador del menú en la tabla interna de OpenERP donde se 
guardan los menús. Este identificador debe ser único. Este campo es obligatorio.
• name: Define el nombre del menú en la jerarquía de menús. Este campo es opcional, si no se 
indica, el menú tomará el nombre de la acción asociada.
• action: Especifica el identificador de la acción que debe haber sido definida en la tabla de acción 
(ir.actions.act_window). Nótese que este campo no es obligatorio: se puede definir elementos de 
menú sin asociarles acción alguna. 
• icon: Especifica qué icono se mostrará para el menú. El icono por defecto es el de una carpeta 
(STOCK_OPEN).   En   el   apéndice   1   se   muestran   los   iconos   disponibles.   Esto   es   útil   para 
personalizar iconos de menú que deben actuar como árboles de directorios (por ejemplo, los 
iconos “Proyectos”, “Recursos Humanos”, ... se han definido de esta forma).
• parent: Especifica el identificador del menú padre del que depende.
• groups: Especifica qué grupo de usuarios puede ver el menú (por ejemplo: groups=”admin”). Si 
se   desea   introducir   más   de   un   grupo,   se   deben   separar   mediante   comas   “,”   (por   ejemplo: 
groups=”admin,user”).
• sequence: es un entero que se usa para ordenar el menú dentro del árbol de menús. Cuanto más 
grande sea el número de sequence, más abajo aparecerá en el árbol de menús. Este argumento no 
es obligatorio: si no se especifica  sequence, el menú toma el valor por defecto: 10. Los menús 
que tengan el mismo valor de sequence se ordenan por orden de creación.
6.1.1.  Módulo ejemplo academy

Estas podrían ser las líneas (dentro del fichero academy_view.xml) que definiesen los menús del 
módulo academy.py:
<menuitem name="Academy" id="menu_academy"/>
<menuitem name="Courses" id="menu_academy_course"
action="action_academy_course" parent="menu_academy"/>
<menuitem name="Classroom" id="menu_academy_classroom"
action="action_academy_classroom" parent="menu_academy"/>
<menuitem name="Courses Categories" id="menu_academy_course_category"
action="action_academy_course_category" parent="menu_academy"/>

Como se puede ver, hay un menú principal (no tiene padre) denominado Academy que se muestra en 
la barra de menús de OpenERP y tres menús que dependen del primero:  Courses,  Classroom  y 
Courses Categories (figura 6.2) que ejecutarán 3 acciones distintas.

Figura 6.2: Menú Academy
6.2.  Las acciones
Las acciones definen el comportamiento del sistema en respuesta a los eventos generado por el 
usuario. Hay diferentes tipos de acciones, las más importantes:
• window: Abre una vista en una nueva ventana/pestaña.
• report: Imprime un informe.
• wizard: Ejecuta un asistente para realizar un determinado trabajo o proceso.
Las acciones se utilizan para capturar los siguientes eventos en el cliente:
• Los dobles clics del ratón del usuario sobre un menú (normalmente se ejecutan acciones del tipo 
window, aunque también pueden ser report o wizard).
• Los clics de ratón del usuario sobre el icono “imprimir” (se ejecuta una acción report) o “acción” 
(se ejecuta una acción wizard).
• El clic en algún botón de una vista que esté asociado a una acción.
Las acciones window son las más habituales.

6.2.1.  Acciones window (apertura de vistas)

Las acciones window (denominadas internamente ir.actions.act_window o ir.acciones.acc_ventena) 
permiten abrir diferentes tipos y modos de vistas (en el apartado “6.3. Las vistas”, se describirán las 
diferencias entre tipos y modos de vistas). La tabla 1 muestra qué modos de vista pueden abrir cada 
tipo de vista.
Tipos de vista Modos de vista permitidos
● tree (árbol)
● Tree (Árbol)
● form (formulario)
● tree (lista)
● form (formulario)
● graph (gráfico)
● Form (Formulario) ● gantt (diagrama de gantt)
● calendar (calendario)
● search (búsqueda)
● inheritance (herencia)
Tabla1: Tipos de vista y modos de vista permitidos

Para ver ejemplos de acciones asociadas a menús, se recomienda seleccionar un menú concreto (por 
ejemplo el menú Empresas / Empresas / Empresas por categorías), y cambiar el modo de vista (en 
este caso a modo de formulario) pulsando Ctrl + L. El resultado es el mostrado en la figura 6.3.
Figura 6.3: Vista en modo formulario del menú “Empresas por categorías”.

Observamos   que   este   menú   denominado  Empresas   por   categoría  tiene   como   padre   el   menú 
Empresas, como icono el  STOCK_INDENT  y no se ha asociado ningún grupo de usuarios. En el 
campo  Acción  este menú está relacionado con una acción  ir.acciones.acc_ventana. Haciendo clic 
sobre el icono de la carpeta del campo  Acción, se accede a la ventana que describe dicha acción 
(figura 6.4).

Figura 6.4: Acción asociada al menú “Empresas por categorías” que 
abre una vista de tipo árbol.

Como puede comprobarse, el tipo de vista para la acción “Empresas por categorías” es “Árbol”, y 
permite los modos de vista “tree” y “form”, es decir las vistas árbol y formulario.

En cambio, la acción “Proveedores” (figura 6.5) asociada al menú Empresas/Empresas/Proveedores, 
abre una vista de tipo “Formulario” y permite los modos de vista “tree” y “form”, lo que indica que  
por defecto abre la vista en modo lista y se puede cambiar más tarde a formulario. Observar como 
esta acción tienen como valor de dominio  [('supplier','=',1)]  que es la condición para hacer el 
filtrado de las empresas que sean proveedores (lista de Python con condiciones).

Figura 6.5: Acción asociada al menú “Proveedores” que 
abre una vista de tipo formulario.

La acción asociada al menú  Empresas/Empresas/Nueva empresa  (figura  6.6) también es de tipo 


formulario pero a diferencia de la anterior, abre por defecto la vista en modo formulario y permite 
más tarde cambiar a la vista en modo lista. Por este motivo al hacer clic este menú nos aparecerá el 
formulario de empresas en blanco.

Figura 6.6: Acción asociada al menú “Nueva empresa”.

Este es un ejemplo genérico de una acción de ventana guardado en el fichero XML:
<record model="ir.actions.act_window" id="action_id_1">
<field name="name">action.name</field>
<field name="view_id" ref="view_id_1"/>
<field name="domain">["list of 3-tuples (max 250 characters)"]</field>
<field name="context">{"context dictionary (max 250 characters)"}</field>
<field name="res_model">Open.object</field>
<field name="view_type">form|tree</field>
<field name="view_mode">form,tree|tree,form|form|tree</field>
<field name="usage">menu</field>
<field name="target">new</field>
</record>

Donde:
• model: Es el nombre del objeto que creamos. En esta caso siempre será “ir.actions.act_window”.
• id: Es el identificador de la acción. Debe ser único.
• name: Es el nombre de la acción (obligatorio).
• res_model: Es el nombre del objeto sobre el que opera la acción.
• view_type: Se debe establecer a “form” para que la acción abra una vista de tipo formulario, y a 
“tree” cuando la acción deba abrir una vista de tipo árbol.
• view_mode: Sólo 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 
más habituales son:
– form,tree: La vista se muestra primero en modo formulario, pero se puede mostrar en modo 
lista clicando el botón Lista.
– tree,form:   La   vista   se   muestra   primero   en   modo   lista,   pero   se   puede   mostrar   en   modo 
formulario clicando el botón Formulario.
– form: La vista se muestra como un formulario y no hay forma de cambiar al modo lista.
– tree: La vista se muestra como una lista y no hay forma de cambiar al modo formulario.
• view_id: Es el nombre de la vista a mostrar cuando se activa la acción. Si este campo no se 
definiese, se usaría el tipo de vista (lista o formulario) asociado al objeto res_model con el campo 
de mayor prioridad (si dos vistas tuviesen la misma prioridad, se utilizaría el primer tipo de vista 
definido).
• domain: Es una lista Python de condiciones utilizada para refinar los resultados de una selección, 
y en consecuencia, para mostrar menos registros en la vista. Las condiciones de la lista se enlazan 
con una cláusula AND y son tuplas Python con 3 valores ('campo','condición','valor'). Un registro 
de la tabla sólo se mostrará en la vista si satisface todas las condiciones.
• context: Es el diccionario contextual que se utilizará en la vista que se abra cuando se active la 
acción. Los diccionarios contextuales se declaran como un diccionario Python. Estos diccionarios 
contienen información de contexto como por ejemplo el idioma, moneda, tarifa, almacén, ... a 
usar.

6.2.2.  El dominio de la acción. Condiciones.

Este parámetro permite regular qué recursos serán visibles en la vista seleccionada (condición). Por 
ejemplo, en el caso de facturas, se puede definir una acción que abra una vista que muestre sólo 
facturas borrador.

Los dominios se escriben en Python: Lista de tuplas, cada una de las cuales tiene tres elementos:
• El campo en el que se debe realizar la condición.
• El operador utilizado para la condición (<, >, =, like).
• El valor de la condición. 
Por ejemplo, si se desea obtener solo facturas en estado “Borrador”, se usaría el siguiente dominio: 
[('state','=','draft')].

6.2.3.  Módulo ejemplo academy

El siguiente código muestra las acciones del módulo academy.
<record model="ir.actions.act_window" id="action_academy_course">
<field name="name">Courses</field>
<field name="res_model">academy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>

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


<field name="name">Classroom</field>
<field name="res_model">academy.classroom</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>

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


<field name="name">Courses Categories</field>
<field name="res_model">academy.course.category</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>

Como se puede comprobar, todas las acciones se almacenan en el modelo “ir.actions.act_window”, 
cada una tiene un identificador id único, cada acción opera (res_model) sobre un modelo distinto 
del módulo academy. Las 3 acciones son de tipo form y permiten los modos de vista tree,form (lista 
y   formulario).   No   tienen   definido   ningún  dominio   (por  lo   que   se  verán   todos   los   registros)  ni 
tampoco se ha forzado a usar ninguna vista en concreto (se usarán las vista por defecto que veremos 
en el siguiente apartado).
6.3.  Las vistas.
Existen dos tipos de vistas distintos: tipo  Árbol  y tipo  Formulario. El tipo de vista  Formulario 
permite a su vez una serie de modos: lista, formulario, calendario, gráfico, ... En la figura 6.7 puede 
verse como, desde el menú Empresas/Empresas, puede accederse directamente a distintos tipos de 
vistas.

Figura 6.7: Menús cuyas acciones abren distintos tipos de vistas.

Desde el menú del recuadro azul (“Empresas por categorías”) se accedería a una vista de tipo árbol 
con una organización interna jerárquica (figura 6.8).
Figura 6.8: Vista de tipo árbol.

Desde el menú del recuadro rojo (“Proveedores”) se accedería a una vista de tipo lista (figura 6.9).

Figura 6.9: Vista en modo lista.

Finalmente, desde el menú del recuadro verde (“Nueva empresa”), se accedería directamente a una 
vista de formulario (figura 6.10).
Figura 6.10: Vista en modo formulario.

Esta es la declaración genérica del registro XML de 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>

Donde:
• id: Es el identificador único de la vista.
• name: Nombre de la vista.
• model:  Modelo  u objeto sobre el  que  se define  la  vista  (el  mismo  que  el  res_model  de  las 
acciones).
• type: Tipo de vista: form, tree, graph, calendar, search, gantt.
• priority: Prioridad de la vista, cuanto más pequeño, más prioridad (por defecto 16).
• arch: Arquitectura o estructura de la vista. Mediante etiquetas XML se define la composición de 
la vista (etiquetas, campos, pestañas, separadores, ...).
6.3.1.  Vistas de formulario

La disposición de los campos en una vista de formulario siempre sigue el mismo principio: Los 
campos se distribuyen en la pantalla siguiendo las siguientes reglas:
• Por defecto, cada campo es precedido por una etiqueta con su nombre.
• Los campos se colocan en la pantalla de izquierda a derecha y de arriba a abajo, de acuerdo al 
orden con el que están declarados en la vista.
• Cada pantalla se divide en cuatro columnas, cada columna es capaz de contener, o bien una 
etiqueta o bien un campo de edición. Puesto que todos los campos de edición son precedidos (por 
defecto) con una etiqueta con su nombre, habrá dos campos (y sus respectivas etiquetas) en cada 
línea de la pantalla. En la figura 6.11, las zonas verdes y rojas, ilustran las cuatro columnas.

Figura 6.11: Colocación por defecto de los campo y sus etiquetas en cuatro columnas.

Pero las vistas también soportan opciones de colocación avanzadas. Un campo puede mostrarse en 
varias columnas con el atributo colspan. En la figura 6.12, se resalta en rojo un único campo con su 
correspondiente etiqueta, que ocupa las cuatro columnas de la pantalla.
Figura 6.12: Una campo ocupa varias columnas.

También se puede efectuar la operación contraria: tomar un grupo de columnas y dividirlas en las 
columnas que se deseen con la etiqueta group y los atributos colspan y col.

Finalmente, hay una forma de distribuir los campos de un objeto en diferentes pestañas (zona azul 
de la figura 6.13) con las etiquetas notebook y page.
Figura 6.13: Otra forma de mostrar los campos mediante pestañas.
a)  Módulo ejemplo academy
Éste es el código para la vista de formulario classroom:
<record model="ir.ui.view" id="view_academy_classroom_form">
<field name="name">academy.classroom.form</field>
<field name="model">academy.classroom</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="academy.classroom">
<field name="name" select="1"/>
<field name="address_ids" select="2"/>
<field name="capacity" />
<field name="state" />
<field name="active" />
</form>
</field>
</record>

Donde los siguientes atributos son comunes a todos los elementos:
• string: Es la etiqueta del elemento.
• nolabel: Sirve para ocultar la etiqueta del campo (valor 1).
• colspan: Sirve para indicar el número de columnas que debe abarcar el campo.
• rowspan: Sirve para indicar el número de filas que debe abarcar el campo.
• col: Número de columnas que este elemento debe asignar a sus elementos hijos.
• invisible: Si su valor se establece a 1, sirve para ocultar completamente este elemento.
• eval: Evalúa este código Python como un contenedor de elemento (por defecto, el contenedor es 
una cadena)
• attrs:   Asignación   Python   que   define   las   condiciones   dinámicas   de   estos   atributos:  readonly, 
invisible, required basados en tuplas de búsqueda u otros campos de valor.
Los elementos que se pueden añadir en una vista pueden ser:

field: Campos con su correspondiente widget según el tipo de campo. Por ejemplo un campo de tipo 
fecha tiene un widget con un calendario para poder seleccionar la fecha con comodidad (figura 6.14
). 

Figura 6.14: 

Los atributos específicos del campo field son:
– string: Etiqueta del campo, también para búsquedas (reemplaza el nombre del campo).
– select: 1 para mostrar el campo de búsqueda normal, 2 sólo para búsqueda avanzada.
– nolabel: 1 para ocultar la etiqueta del campo.
– required: Reemplaza al atributo “required” definido en el modelo.
– readonly: Reemplaza al atributo “readonly” definido en el modelo.
– password: True para ocultar los caracteres escritos en este campo.
– context: Código Python para declarar un diccionario contextual.
– domain:   Código   Python   para   declarar   una   lista   de   tuplas   con   condiciones   para   restringir 
valores (usado en campos relacionales).
– on_change: Método Python que se ejecuta cuando se cambia el valor del campo.
– groups: Lista de grupos de usuarios separados por comas (id) autorizados a ver dicho campo.
– widget:   Selecciona   un   widget   alternativo   al   proporcionado   de   forma   automática 
(one2many_list,   many2many,   url,   image,   float_time,   reference,   text_wiki,   text_html, 
progressbar). En las figuras 6.14 a 6.20, pueden verse algunos ejemplos de widgets.

Figura 6.15:    
Widget booleano

Figura 6.16: Widget selection

Figura 6.17: Widget number
Figura 6.18: Widget one2many

Figura 6.19: Widget many2many

Figura 6.20: Widget url

properties: Widget dinámico que muestra todas las propiedades disponibles.

button:   Widget   en   forma   de   botón   sobre   el   que   se   puede   hacer   clic   que   está   asociado   a 
determinadas acciones. Atributos específicos:
– type: Tipo de botón. Puede ser “workflow” (flujo de trabajo ­por defecto­), “object” o “action”.
∙ workflow: Mediante este tipo se puede cambiar el estado del registro pasando por una serie 
de estados predefinidos.
∙ object:
∙ action: Tiene asociada una acción que se ejecuta cuando se hace clic sobre el botón. 
– name:   Señal   de   flujo   de   trabajo,   nombre   de   función   (sin   paréntesis)   o   acción   a   llamar 
(dependiendo del tipo).
– confirm: Texto de mensaje de confirmación cuando se hace clic.
– states: Lista de estados separados por comas, en los que dicho botón se muestra.
– icon: Icono opcional (se pueden usar todos los iconos de GTK STOCK, por ejemplo gtk­ok).
separator: Línea de separación horizontal para estructurar vistas, con etiqueta opcional.

newline: Añade espacio en blanco para completar la línea actual de la vista y saltar a la siguiente.

label: Título de texto libre o leyenda en el formulario.

group: Utilizado para organizar campos en grupos con etiquetas opcionales (añade marco).

notebook, page: Los elementos notebook son contenedores de pestaña para los elementos page que 
definen cada una de las pestañas. Atributos:
• name: Etiqueta para la pestaña.
• position: posición de la pestaña en el notebook: inside (dentro), top (arriba), bottom (abajo), left 
(izquierda), right (derecha).
Este es el código para la vista de formulario de course:
<record model="ir.ui.view" id="view_academy_course_form">
<field name="name">academy.course.form</field>
<field name="model">academy.course</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="academy.course">
<field name="name" select="1"/>
<field name="category_id" select="2"/>
<field name="classroom_id" />
<field name="dificulty" />
<field name="hours" />
<field name="state" />
<field name="description" colspan="4" />
</form>
</field>
</record>

Este es le código para la vista de tipo formulario de course_category:
<record model="ir.ui.view" id="view_academy_course_category_form">
<field name="name">academy.course.category.form</field>
<field name="model">academy.course.category</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="academy.course.category">
<field name="name" select="1"/>
<field name="parent_id" select="2"/>
</form>
</field>
</record>

Como puede comprobarse, ambos  campos aparecerán en las  opciones  de búsqueda. El primero 


(name) en la búsqueda normal, y el segundo (parent_id) en la búsqueda avanzada.

6.3.2.  Vistas de lista o árbol

Estas vistas se utilizan cuando se trabaja en modo lista (con objeto de visualizar varios recursos a la 
vez) y en la pantalla de búsqueda. Son más simples que las vistas de formulario y tienen menos 
opciones. Incluyen elementos field, se crean con el tipo tree, y tienen un elemento padre <tree> en 
lugar de <form> usado anteriormente.

Entre los atributos que se le pueden añadir caben destacar los siguientes:
• colors: Listas de colores mapeados a Python.
• editable: top o botton para permitir editar el lugar en el que colocarlo.
• toolbar: Establecer a True para mostrar el nivel superior de la jerarquía de objetos como una 
barra de herramientas lateral (por ejemplo, los menús).
Los elementos permitidos son: field, group, separator, tree, button, filter y newline.
a)  Módulo ejemplo academy
Este es el código para la vista de tipo lista de classroom:
<record model="ir.ui.view" id="view_academy_classroom_tree">
<field name="name">academy.classroom.tree</field>
<field name="model">academy.classroom</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="academy.classroom">
<field name="name"/>
<field name="address_ids"/>
<field name="capacity"/>
<field name="state"/>
<field name="active"/>
</tree>
</field>
</record>

Como puede comprobarse, se mostrarán cinco campos.

Este es el código de la vista de tipo lista de curso:
<record model="ir.ui.view" id="view_academy_course_tree">
<field name="name">academy.course.tree</field>
<field name="model">academy.course</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="academy.course">
<field name="name"/>
<field name="category_id"/>
<field name="classroom_id"/>
<field name="dificulty"/>
<field name="hours"/>
<field name="state"/>
<field name="description"/>
</tree>
</field>
</record>

En este caso puede comprobarse como se hace referencia a los objetos  category  y  classroom  a 


través de los campos category_id y classroom_id.

El código de la vista de tipo lista de course_category es el siguiente:
<record model="ir.ui.view" id="view_academy_course_category_tree">
<field name="name">academy.course.category.tree</field>
<field name="model">academy.course.category</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="academy.course.category">
<field name="name"/>
<field name="parent_id"/>
</tree>
</field>
</record>

En   este  caso   puede  observarse   como  el   objeto  hace   referencia   a  sí  mismo   a  través   del   campo 
parent_id.

La figura 6.21 muestra el resultado del código anterior.
Figura 6.21: Ejemplo de Vista de lista o árbol.
7.  Anexo 1.
Lista de iconos disponibles para el parámetro icon de las vistas XML.

STOCK_ABOUT,   STOCK_ADD,   STOCK_APPLY,   STOCK_BOLD,   STOCK_CANCEL, 


STOCK_CDROM,   STOCK_CLEAR,   STOCK_CLOSE,   STOCK_COLOR_PICKER, 
STOCK_CONNECT,   STOCK_CONVERT,   STOCK_COPY,   STOCK_CUT,   STOCK_DELETE, 
STOCK_DIALOG_AUTHENTICATION, STOCK_DIALOG_ERROR, STOCK_DIALOG_INFO, 
STOCK_DIALOG_QUESTION,   STOCK_DIALOG_WARNING,   STOCK_DIRECTORY, 
STOCK_DISCONNECT,   STOCK_DND,   STOCK_DND_MULTIPLE,   STOCK_EDIT, 
STOCK_EXECUTE,   STOCK_FILE,   STOCK_FIND,   STOCK_FIND_AND_REPLACE, 
STOCK_FLOPPY,   STOCK_GOTO_BOTTOM,   STOCK_GOTO_FIRST,   STOCK_GOTO_LAST, 
STOCK_GOTO_TOP,   STOCK_GO_BACK,   STOCK_GO_DOWN,   STOCK_GO_FORWARD, 
STOCK_GO_UP,   STOCK_HARDDISK,   STOCK_HELP,   STOCK_HOME,   STOCK_INDENT, 
STOCK_INDEX,   STOCK_ITALIC,   STOCK_JUMP_TO,   STOCK_JUSTIFY_CENTER, 
STOCK_JUSTIFY_FILL,   STOCK_JUSTIFY_LEFT,   STOCK_JUSTIFY_RIGHT, 
STOCK_MEDIA_FORWARD,   STOCK_MEDIA_NEXT,   STOCK_MEDIA_PAUSE, 
STOCK_MEDIA_PLAY,   STOCK_MEDIA_PREVIOUS,   STOCK_MEDIA_RECORD, 
STOCK_MEDIA_REWIND,   STOCK_MEDIA_STOP,   STOCK_MISSING_IMAGE, 
STOCK_NETWORK,   STOCK_NEW,   STOCK_NO,   STOCK_OK,   STOCK_OPEN, 
STOCK_PASTE,   STOCK_PREFERENCES,   STOCK_PRINT,   STOCK_PRINT_PREVIEW, 
STOCK_PROPERTIES, STOCK_QUIT,STOCK_REDO, STOCK_REFRESH, STOCK_REMOVE, 
STOCK_REVERT_TO_SAVED,   STOCK_SAVE,   STOCK_SAVE_AS, 
STOCK_SELECT_COLOR,   STOCK_SELECT_FONT,   STOCK_SORT_ASCENDING, 
STOCK_SORT_DESCENDING,   STOCK_SPELL_CHECK,   STOCK_STOP, 
STOCK_STRIKETHROUGH,   STOCK_UNDELETE,   STOCK_UNDERLINE,   STOCK_UNDO, 
STOCK_UNINDENT,   STOCK_YES,   STOCK_ZOOM_100,   STOCK_ZOOM_FIT, 
STOCK_ZOOM_IN, STOCK_ZOOM_OUT, terp­account, terp­crm, terp­mrp, terp­product, terp­
purchase,  terp­sale,  terp­tools,  terp­administration,  terp­hr, terp­partner, terp­project,  terp­report, 
terp­stock

Potrebbero piacerti anche