* Luego de instalado y reiniciado el sistema actualizar a los últimos paquetes disponibles sudo apt-get update sudo apt-get upgrade *
Instalar el software que necesitaremos para el taller sudo apt-get install eclipse pgadmin3
Instalar Odoo 8.0 -------------------*
Descargue la última versión para Linux, desde la línea de comandos ejecutar:
sudo su wget -O - https://nightly.odoo.com/odoo.key | apt-key add echo "deb http://nightly.odoo.com/8.0/nightly/deb/ ./" >> /etc/apt/sources.list apt-get update apt-get install odoo apt-get install -f * Para no generar conflictos entre la versión que se corre a través de eclipse y la existente en el sistema, debe desactivar el inicio del servicio Odoo con el comando sudo update-rc.d odoo disable sudo /etc/init.d/odoo stop *
Configurar el usuario de base de datos `odoo` con clave `odoo` sudo su postgres createuser -d -S -R odoo psql -c "ALTER USER odoo WITH PASSWORD 'odoo';"
## Configuración de Eclipse como ambiente de desarrollo *
Instalar el módulo PyDEV para desarrollo en Python * Ingresar a la opción del menú **Help > Install New Software** * En el cuadro de dialogo diligenciar **Work with:** con el valor http://pydev.org/updates y presionar la tecla **enter** * Desactivar el campo **Show only the latest versions of available software** para que muestre todas las versiones disponibles * Seleccionar para instalar la **versión 3.5.xxxxxx**, con versiones más actualizadas no se puede hacer debug de Odoo. * Continue con el proceso de instalación del plugin *
Instalar el módulo Web Tools para incluir editor para archivos XML * Ingresar a la opción del menú **Help > Install New Software** * En el cuadro de dialogo diligenciar **Work with:** con el valor http://download.eclipse.org/webtools/repository/indigo y presionar la tecla **enter** * Seleccionar en la lista que aparece seleccione para instalar **Eclipse XML
Editors and Tools**, se ha probado con la versión 3.3.2, pero cualquier versión reciente debe funcionar. * Continue con el proceso de instalación del plugin ## Crear un proyecto y configurar el entorno para ejecutar Odoo En eclipse crear un nuevo proyecto PyDEV, si no se encuentra configurado el entorno de python eclipse le preguntará si desea auto configurarlo, ud debe hacer click en el botón **auto config** y aceptar los valores por defecto. Ingrese a través del menú a la opción **Run > Run configurations** Haga click en el icono (parte superior izquierda) **New Launch Configuration** En **Name** digite **servidor Odoo** En **Project** seleccione el nombre del nuevo proyecto En **Main Module** digite **/usr/bin/openerp-server**, este es el comando para iniciar el openerp En la pestaña **Arguments** en el campo **program arguments** ingrese: -r odoo -w odoo --db_host=localhost Estos son los parámetros que se pasarán al servidor de Odoo - -r es el nombre del usuario para conectarse a PostgreSQL - -w la clave asignada para conectarse a PostgreSQL -
Luego haga click en el boton **Apply** y **Run** Otro parámetro importante para el resto del curso es **`--addons-path`** el cual indica al servidor donde buscar el código de módulos adicionales de Odoo, podemos indicar que use la carpeta del proyecto eclipse con la variable **`$ {project_loc:NombreDeMiProyecto}`**, quedando el parámetro: -r openerp -w openerp --db_host=localhost --addons-path=$ {project_loc:NombreDeMiProyecto} **Nota**: :white_up_pointing_index: Recuerde cambiar **NombreDeMiProyecto** por el nombre del proyecto que usted acaba de crear Si uds adiciona este campo en este momento, al arrancar el servidor fallará indicando que la carpeta no es válida como addons-path, esto es porque aún no existen modulos Odoo en el proyecto. openerp-server: error: option --addons-path: The addons-path '/home/usuario/workspace/lecciones_odoo' does not seem to a be a valid Addons Directory! También puede ejecutar el servidor llamando un código disponible fuera de eclipse, por ejemplo: -r openerp -w openerp --db_host=localhost --addonspath=/directorio/otros/modulos Si ud indica un directorio que no existe el error que desplegará será: openerp-server: error: option --addons-path: The addons-path '/home/carpeta_vacia' does not seem to a be a valid Addons Directory! :happy_person_raising_one_hand: Uds puede utilizar el --addons-path para indicar donde se encuentra el código de la lección que este estudiando, ejemplo. -r openerp -w openerp --db_host=localhost --addons-
path=/home/usuario/curso-taller-openerp/lecciones/01/src [Mayor información acerca de los parámetros de arranque del servidor Odoo] (https://www.odoo.com/documentation/8.0/reference/cmdline.html#running-the-server) * *
Abrir en el navegador: http://localhost:8069 y accederá a la interfaz de Odoo Ahora puede crear una base de datos haciendo click en **Manage Databases** * Master Password: es el password de super administrador de Odoo por defecto *admin* * New Database Name: Indique el nombre de la nueva base de datos a crear * Load Demonstration data: Indica si quiere que los módulos se instalen con datos de prueba * Default language: Indica el lenguaje a instalar por defecto * Admin password: Indica el password de administración de la nueva base de datos * El proceso de creación tomará algunos segundos, luego se presentará la pantalla con el listado de módulos disponibles en el sistema, puede elegir uno cualquiera ejemplo CRM ## Continuar con las lecciones del curso Ya con esto esta listo Odoo y eclipse para continuar con el resto de las lecciones, El objetivo es ir creando nuestro módulo del taller en eclipse a medida que vamos avanzando de lección, cada lección tiene un archivo **README** que contiene la teoria de la lección y una carpeta **src** donde puede esta el código de ejemplo para lección, este código lo puede copiar y pegar en su proyecto de eclipse a medida que avanza en las lecciones. Al final de cada lección existe un ejercicio propuesto el cual le ayudará a afianzar lo visto en la lección. ## Ejercicio Propuesto Explore la instalación de Odoo que realizó en las secciones anteriores y familiarícese con la interfaz y los diferentes elementos de la misma (menús, formularios, busquedas, listados, etc)
LecciOn 01: Estructura de un mOdulo Odoo ======================================== Directorios y archivos bAsicos -----------------------------El mOdulo de Odoo tiene la siguiente estructura bAsica: └── mi_modulo ├── __init__.py ├── mi_modulo.py ├── mi_modulo_view.xml └── __openerp__.py - **`__init__.py`**: Define el paquete python. El archivo incluye los mOdulos que hacen parte del paquete. import mi_modulo
- **`__openerp__.py`**: Define los metadatos del modulo Odoo. En la documentaciOn de referencia de Odoo puede encontrar [mayor informaciOn de como definir un mOdulo] (https://www.odoo.com/documentation/8.0/reference/module.html) {
- **name**: Nombre del mOdulo en Odoo - **data**: Archivos xml/csv a ser cargados en el momento de la instalaciOn/actualizaciOn del mOdulo, de esta forma se puede cargar datos iniciales, configuraciOn de vistas, flujos de trabajo, configuraciOn de seguridad. - **demo**: Archivos xml/csv a ser cargados en el momento de la instalaciOn del mOdulo pero cuando la base de datos se creO con la opciOn de cargar datos de ejemplo. - **depends**: Lista los mOdulos que deben estar instalados en el sistema como requisito para instalar este mOdulo. - **`mi_modulo.py`**: MOdulo python que contiene los objetos de negocio de nuestro mOdulo. Ejemplo: # -*- coding: utf-8 -*from openerp import models, fields class mi_modulo_mi_tabla(models.Model): _name = "mi_modulo.mi_tabla" name = fields.Char('Nombre', size=25) description = fields.Char('DescripciOn', size=255) - **`mi_modulo_view.xml`**: Archivo XML que contiene la definiciOn de las vistas, acciones y menús a ser desplegados. Ejemplo: mi_modulo.mi_tablami_modulo.mi_tabla
Tablami_modulo.mi_tablatree,form La estructura y nombre de archivos puede ajustarse de acuerdo a las preferencias del desarrollador, lo importante es ajustar las referencias a los archivos a cargarse en el momento de la instalaciOn en el archivo __openerp__.py, por ejemplo: mi-carpeta-de-modulos ├── nombre_modulo │  ├── __init__.py │  ├── models │  │  ├── __init__.py │  │  └── nombre_modulo.py │  ├── __openerp__.py │  └── views │  └── nombre_modulo.xml ├── otro_modulo │  ├── __init__.py │  ├── models.py │  ├── __openerp__.py │  └── views.xml └── mi_modulo ├── __init__.py ├── __openerp__.py ├── mi_modulo.py └── mi_modulo_view.xml Carga de datos con archivos XML -------------------------------Como se ve en el secciOn anterior un archivo XML se puede usar para cargar datos al Odoo en el momento de instalar o actualizar un mOdulo, por cada registro que se desea cargar en la base de datos se debe utilizar las etiquetas **record** y **field**. Usuario creadousername
Lección 02: Modelos Odoo ======================== Esta lección explica la forma básica de construir Modelos Odoo y definir sus campos. [TOC] Mi primer Modelo Odoo --------------------Cada Modelo contiene campos y métodos. Para crear un modelo solo es necesario crear una clase python que herede de **models.Model**, models.Model es la clase base que se encarga de servir como [ORM - Object Relational Mapping] (http://es.wikipedia.org/wiki/Mapeo_objeto-relacional) y gestiona el acceso y almacenamiento de datos en PostgreSQL. Estructura básica de un objeto de negocio: ```python class mi_modulo_mi_objeto_de_negocio(osv.osv): _name = 'mi_modulo.mi_objeto_de_negocio' _description = 'descripción del objeto de negocio' nombre_campo_1 = fields.tipo_dato(parametros), nombre_campo_2 = fields.tipo_dato(parametros), ``` Ejemplo: ```python class biblioteca_libro(models.Model): _name = 'biblioteca.libro' _description = 'Contiene la información de libros'
```
name = fields.Char('Titulo', size=255, help='Título del libro') active = fields.Boolean('Active', help='Activo/Inactivo')
En este ejemplo se crea el objeto de negocio Libro que corresponde al módulo Bibloteca, el objeto esta compuesto por dos campos *name* y *active*, esta definición va a crear una tabla **biblioteca_libro** en la base de datos PostgreSQL para almacenar todos los registros de este objeto de negocio. - **_name** indica el nombre con el cual se va a hacer referencia a este objeto de negocio la plataforma OpenERP el objeto se llama **biblioteca.libro**. - **_description** contiene la descripción del Modelo como forma de documentar el mismo. No usar Tíldes o carácteres especiales en esta sección. Definición de campos -------------------Los campos que hace parte del Modelo se definen como atributos de la clase python y son instancias del módulo python **fields.Field**. Al crear un campo en el Modelo puede definir los siguientes atributos básicos:
* **string:** Etiqueta/label que se despliega en la interfaz de usuario * **help:** Ayuda que se despliega en la interfaz como un tooltip * **readonly:** Indica que el campo es de solo lectura, por defecto *False* * **required:** Indica que el campo es obligatorio, por defecto *False* * **index:** Crea un [indice en la base de datos](http://es.wikipedia.org/wiki/ %C3%8Dndice_%28base_de_datos%29), por defecto *False* Puede consultar [mayor información acerca de los campos que se pueden crear en Odoo](https://www.odoo.com/documentation/8.0/reference/orm.html#fields) en la documentación de referencia del sitio de Odoo. ### Tipos de datos Odoo permite crear diferentes tipos de campos en un Modelo, los básicos son: -
Ejemplo: ```python class biblioteca_libro(models.Model): _name = 'biblioteca.libro' _description = 'Contiene la información de libros' name = fields.Char('Titulo', size=255, help='Título del libro') active = fields.Boolean('Active', help='Activo/Inactivo') descripcion = fields.Text('Descripción') fecha_publicacion = fields.Date('Fecha', help='Fecha de publicación') precio = fields.Float('Precio', help='Precio de compra', digits=(10,2)) state = fields.Selection( [ ('solicitud', 'Solicitado'), ('en_compra', 'Proceso de compra'), ('adquirido', 'Adquirido'), ('catalogado', 'Catalogado'), ('baja', 'De baja') ], 'Estado', ) ``` ### Tip El campo llamado **active** tiene un significado especial en la plataforma, por defecto la interfaz que lista los registros del Modelo no muestra los registros que tengan el valor de active igual a *False*. Desplegar Modelos en la interfaz --------------------------------
1. Para visualizar instalar el módulo estaba instalado y lección anterior y
un Modelo en la interfaz web, lo primero que se requiere es donde esta el código del Modelo (o actualizar si el módulo ya tiene cambios en el código), siguiendo las instrucciones de la usando el código de ejemplo de la lección.
1. Luego se requiere crear una entrada de menú (menu item) que permita acceder al Modelo, para esto debe 1. Ingresar a *Técnico >> Estructura de la base de datos >> Modelos*, en esta página se despliega un listado de todos los Modelos instalados en Odoo. 1. Buscar y seleccionar el Modelo **biblioteca.libro**, en la página del Modelo se despliega la metadata que almacena Odoo acerca del Modelo creado a través del código python 1. Al final de la página aparece un botón llamado **Crear un menú**, hacer clic en él y llenar el formulario con los siguientes datos: - Nombre del menú: **Libro** - Menú padre: **Configuración/Configuración** El menú padre **Configuración/Configuración** es uno ya existente en la plataforma, se puede usar uno cualquiera de los que existe o crear uno nuevo. 1. Recargar la página, luego de esto podrá observar que se despliega el submenú *Configuración* y el menú item *Libro*. Al hacer click en él se despliega el listado de libros y el botón para crear un nuevo registro. Estas vistas son autogeneradas, más adelante en el taller se indica como construir las vistas con código XML. Ejercicio propuesto ------------------Tomando como base el código fuente disponible en la lección: 1. Revisar la metadata que almacena Odoo para los Modelos y Campos de los módulos instalados. 1. Explore la base de datos PostgreSQL utilizando el programa *pgadmin3*, busque y revise la tabla *biblioteca_libro*, hacer una captura de pantalla para tenerlo como referencia y poder comparar la tabla luego de realizar los cambios de los siguientes ejercicios de esta lección. 1. Adicionar nuevos estados al campo de tipo selección *state*: solicitado, proceso de compra, adquirido. 1. Adicionar los campos: -
isbn tipo char tamaño 13 paginas tipo integer fecha_compra tipo date nombre_autor tipo char tamaño 255
1. Adicionar un texto de ayuda para cada uno de los campos que no lo tenga y verifique en la interfaz que se despliega. 1. Crear un nuevo Modelo llamado **biblioteca.prestamo** y adicione los campos: -
fecha tipo datetime duracion_dias tipo integer fecha_devolucion tipo datetime.
1. Crear un menú de acceso al objeto **biblioteca.libro_prestamo** que tenga como menú padre **Configuración/Configuración**. 1. Adicione, modifique y elimine registros para el Modelo **biblioteca.libro**,
revise que pasa cuando el campo active esta en True o en False. 1. Explore la base de datos utilizando el programa *pgadmin3*, busque y revise como ha cambiado la tabla *biblioteca_libro* luego de los cambios realizados en el módulo.
Lección 03: Vistas Básicas ============================= [TOC] Para ingresar, actualizar, eliminar y desplegar registros para los Modelos definidos en Odoo se hace uso de la interfaz web, esta interfaz se define a través de vistas. Al crear un módulo la interfaz de usuario se define a través de archivos XML, estos archivos son cargados en la base de datos en el momento de instalarse el módulo. La interfaz web es creada dinámicamente utilizando la configuración de vistas para cada Modelo disponible en la base de datos a partir de los archivos XML instalados. Existen diferentes tipos de vistas disponibles en OpenERP, un Modelo puede tener asociadas varias vistas, las vistas básicas son: - form: formulario - tree: listado/árbol - search: búsqueda Cada tipo de vista permite una presentación diferente de los datos almacenados. Para el despliegue de las vistas se utiliza comunmente un enlace desde el menú de opciones de la interfaz web, estos menús son creados en conjunto con las vistas en el XML y cargadas posteriormente a la base de datos. En la lección anterior creamos un menú item directamente en la base de datos, pero para poder replicar este item en otras instalaciones de Odoo, es necesario que exista la definición del menú en el código de un módulo. Definición de una vista ----------------------La estructura para la creación de una vista a través de un archivo XML es la siguiente: Nombre de la vistanombre_modulo.nombre_modelo . . .
- **`name`**: Es el nombre con el cual se identifica la vista - **`model`**: Indica el modelo al cual esta vista estará enlazado - **`priority`**: Indica el orden de prioridad de la vista para el Modelo, la vista con menor prioridad será la vista por defecto a utilizarse. Por defecto el valor es 16. También se usa para indicar el orden en que se aplican los cambios cuando se hereda la vista. - **`arch`**: Indica la estructura de la vista, dentro de este tag se coloca en forma de documento XML la definición de la vista, esta puede cambiar de acuerdo al tipo de vista definido. [Más información acerca de la definición de vistas] (https://www.odoo.com/documentation/8.0/reference/views.html#common-structure) Vista tipo listado -----------------El tipo de vista listado o tree, se utiliza para visualizar el listado de registros que existe en la base de datos para el Modelo que se visualiza en la vista; en este tipo de vista se deben adicionar los campos más relevantes para ser desplegados. Cada lista puede ser definido utilizando los siguientes elementos básicos: - **`default_order`**: Permite indicar el criterio de organización de los registros del listado. - **`colors`**: Se puede asignar un color a la fuente de letra a los registros que cumplan con una condición especifica. [Consulte los colores válidos] (http://www.w3.org/TR/css3-color/#colorunits) - **`fonts`**: Se puede asignar un estilo de fuente de letra a los registros que cumplan con una condición especifica. Los estilos permitidos son `bold`, `italic`, `underline`. - **`editable`**: Se puede indicar que el registro sea editable desde la vista tipo lista sin necesidad de abrir el objeto en un formulario. Los valores permitidos son `top` o `bottom`. [Más información acerca de la definición de vistas tipo listado] (https://www.odoo.com/documentation/8.0/reference/views.html#lists) Ejemplo para la creación de una vista tipo lista: biblioteca.libro.treebiblioteca.libro Vista tipo formulario --------------------Los formularios permiten creación y/o edición de registros, cada formulario puede
ser definido utilizando los siguientes elementos básicos: - **`notebook`**: Crea un contenedor de pestañas (tabs). - **`page`**: Crea una pestaña dentro del *notebook*, requiere un atributo `string` con el nombre de la pestaña. - **`newline`**: Crea un salto de línea, obligando a que los elementos siguientes se ubiquen en la siguiente línea. - **`separator`**: Crea una linea separadora con una etiqueta definida en el atributo `string` - **`label`**: Crea una etiqueta de texto - **`group`**: Permite agrupar elementos y opcionalmente asignar una etiqueta - atributo **`colspan`**: Indica el número de columnas que va a tomar el grupo - atributo **`col`**: Indica el número de columnas que el elemento va a contener para organizar los elementos incluidos en el grupo. Por defecto un grupo tiene dos columnas. - **`sheet`**: Permite agrupar los elementos de un formulario dentro de un recuadro que emula una página impresa, dandole margen al formlario. - **`header`**: Permite organizar una cabecera donde incluir botones de acción y desplegar el estado del objeto. - **`HTML Tags`**: También se pueden adicionar tags HTML para personalizar la estructura de la vista. - **`field`**: Despliega o permite la edición del dato del registro para un campo especifico del Modelo. - atributo **`name`**: Obligatorio. Indica el nombre del campo en el modelo. - atributo **`widget`**: Permite indicar que el campo tenga una visualización diferente a la predefinida. Algunos widgets son: `statusbar`, `progressbar`, `selection` - atributo **`class`**: Indica una clase CSS usada para darle estilo al campo. Algunas clases predefinidas son: `oe_inline`, `oe_left`, `oe_right`, `oe_read_only`, `oe_edit_only`, `oe_no_button`, `oe_avatar`. [Más información acerca de la definición de vistas tipo formulario] (https://www.odoo.com/documentation/8.0/reference/views.html#forms) Ejemplo para la creación de una vista tipo formulario: biblioteca.libro.formbiblioteca.libro Window Actions y Menu Items --------------------------Los Actions indican a Odoo como debe responder frente a una acción del usuario, existen varios tipos de actions, el más utilizado es el Window Action, el cual define que modelo se va a desplegar, que vistas van a estar disponibles y la configuración general de las mismas. Para generar un action utilizamos el `record` para el model `ir.actions.act_window` a continuación se listan los atributos más relevantes para configurar el action: - **`name`**: Un nombre para el action, se utiliza para desplegarse en la interfaz web. - **`res_model`**: Se indica el nombre del Modelo del cual se va a presentar la información. - **`view_type`**: Cuando se abre el tipo de vista listado, este parámetro indica si este se abre como un listado normal `form` o como un árbol desplegable `tree`. - **`view_mode`**: Indica los tipos de vista que van a estar disponibles. - **`limit`**: Se indica cuantos registros se van a desplegar en los listados. Por defecto se despliegan 80. - **`view_id`**: Se puede indicar el ID de una vista especifica para ser desplegada. [Más información acerca de la definición de window actions] (https://www.odoo.com/documentation/8.0/reference/actions.html#window-actions-iractions-act-window) Ejemplo para la creación de un action a ser utilizado en el menú: Catálogo de Librosbiblioteca.libroformtree,form10 La estructura de menús se define al igual que las vistas usando un documento XML. Para este caso vamos a usar el tag `menuitem`. - atributo **`id`**: Identificador del menuitem para ser referenciado en otros menuitems - atributo **`name`**: Indica el nombre a desplegarse en el menú. - atributo **`parent`**: Indica el ID de otro menuitem que va a ser el item padre de este menu, esto permite generar el árbol de navegación. Si no tiene padre, el
menuitem va a aparecer en la barra superior de navegación. - atributo **`action`**: Indica el ID de una acción que va a ser ejecutada. Esta acción indica que Modelo va a ser desplegado y que vistas se van a incluir. Acontinuación un ejemplo de como crear menú para ul módulo: Ejercicios propuestos --------------------Tomando como base el código fuente disponible en la lección: - Adiciones varios registros en el menú *Libros*. :white_up_pointing_index: Cuando esta en la vista formulario de un registro puede hacer click en la opción que aparece en la parte superior central *Más >> Duplicar*, para crear una copia del registro actual. - Cambie el estado de algunos registros a *solicitud* o *catalogado* y vea como cambia de colores el listado de libros. - Adicione a la vista tipo listado del Modelo *biblioteca.libro* el campo `fecha_publicacion`. - Asigne nuevos colores a la vista tipo listado para los estados en *Proceso de compra* y *De baja* del Modelo *biblioteca.libro* - Ingrese al menú *Biblioteca >> Catálogo >> Editar precios* y edite los precios de los libros. - Haga clic en el botón crear y adicione un nuevo registro - Modifique la vista con el id `precio_libro_tree` y coloque `editable="bottom"` - Actualice y cree un nuevo registro. Nota la diferencia? - Ingrese al menú *Configuración >> Técnico >> Interfaz de usuario >> Elementos menú*, búsque los menus creados para el módulo y revise los datos que se almacenan en la base de datos a partir del XML. - Ingrese al menú *Configuración >> Técnico >> Interfaz de usuario >> Vistas*, búsque las vistas creadas para el Modelo *biblioteca.libro* y revise los datos que se almacenan en la base de datos a partir del XML. - Ingrese al menú *Configuración >> Técnico >> Acciones >> Acciones de ventana*, búsque las acciones creadas para el Modelo *biblioteca.libro* y revise los datos que se almacenan en la base de datos a partir del XML. - Active el **modo desarrollador**, ingresando a la opción *Acerca de Odoo* que se despliega en el menú de la parte superior derecha en la barra negra de navegación donde dice *Administrador*. - Abra la vista listado del Modelo *biblioteca.libro*, en la parte superior del formulario, abajo del menú de navegación verá que aparece un campo de selección que dice *Depurar vista#xx*, haga clic y seleccione la opción *Editar TreeVista*. - En el cuadro de dialogo que se despliega puede observar que aparece la vista definida, modifique el campo en la pestaña *estructura* y adicione el campo `` - Guarde y cierre el cuadro de diálogo y recargue la página. Verá que la interfaz se actualiza basado en el dato actualizado de la base de datos. - Ahora reinicie el servidor Odoo en el eclipse y note como los cambios que hizo en la base de datos se reversan luego de actualizado el módulo. - Crear el item del menú para el Modelo *biblioteca.prestamo* - Cree las vistas tree y form para el Modelo *biblioteca.prestamo*
Lección 04: Reglas Básicas y Restricciones en Modelos ===================================================== [TOC] Los Modelos en Odoo permiten configuraciones adicionales que agregan reglas que permiten manejar la consistencia de los datos basado en las necesidades de la aplicación. Campos requeridos, de solo lectura y valores por defecto -------------------------------------------------------Cada campo puede ser definido como requerido, de solo lectura o asignarsele un valor por defecto, un campo del Modelo puede asignarsele uno o varios de estos atributos. A continuación un ejemplo: ```python import random class biblioteca_libro(models.Model): _name = 'biblioteca.libro' def _precio_aleatorio(self): return random.random() name = fields.Boolean('Active', help='Activo/Inactivo', required=True) active = fields.Boolean('Active', help='Activo/Inactivo', default=True) fecha_publicacion = fields.Date('Fecha de Publicación', help='Fecha de publicación', default=fields.Date.today) precio = fields.Float('Precio', help='Precio de Compra', digits=(10, 2), default=_precio_aleatorio, readonly=True) isbn = fields.Char('ISBN', size=255, help="International Standard Book Number", copy=False) ``` - Atributo **`required`** se usa para indicar si el **campo es obligatorio** o no en la creación/edición de un registro en el Modelo. True indica que el campo es requerido y False que el campo es no requerido. Si no se define el atributo required en el campo, por defecto toma el valor de required = False. En la interfaz web el campo va a tener un fondo de color azul claro que va a indicar que el campo es obligarotio - Atributo **`readonly`** se usa para indicar si el campo **puede o no ser editable** por el usuario. use True si el campo es no editable y False que pueda ser editable. Si no se define el atributo readonly en el campo, por defecto toma el valor de readonly = False. - Atributo **`default`** se usa para indicar el valor por defecto que va a tener el campo cuando se cree un registro nuevo. Se puede indicar el valor a tomarse o una función que retornaría el valor por defecto a utilizarse. En este diccionario se
adiciona como llave el nombre del campo y como valor lo que deseamos sea el valor por defecto o una función que hace el cálculo del mismo. Se pueden utilizar funciones lambda o métodos de la clase. Debe recordar que los métodos de la clase deben estar previamente definidos para poder utilizarlos. [Más información acerca de valores por defecto] (https://www.odoo.com/documentation/8.0/howtos/backend.html#default-values) - Atributo **`copy`** se usa para indicar que cuando se duplique un registro el campo debe o no ser copiado. `copy=False` evita que se copie, por defecto el valor es `True` Restricciones de Modelo: @api.constrains ---------------------------------------Si se desea que los Modelos tengan restricciones que sean verificadas antes de almacenar los registros, estas se deben definir como métodos de la clase donde se utiliza el decorador @api.constrains. ```python from openerp.exceptions import ValidationError import datetime class biblioteca_libro(models.Model): _name = 'biblioteca.libro' @api.one @api.constrains('fecha_publicacion','fecha_compra') def _check_fechas(self): present = datetime.now() if self.fecha_compra and self.fecha_compra > present: raise ValidationError("Fecha de compra incorrecta") if self.fecha_publicacion and self.fecha_publicacion > present: raise ValidationError("Fecha de publicación incorrecta") ``` Restricciones SQL: _sql_constraints ----------------------------------Otro tipo de restricción que se puede utilizar para programar validaciones es a través del diccionario _sql_constraints, donde se pueden adicionar restricciones programadas con sentencias SQL. _sql_constraints = [ ('unique_isbn','unique(isbn)','El ISBN debe ser único'), ] *En este ejemplo se restringe al usuario la duplicación de registros validados por el campo isbn definido como campo único.* Las restricciones SQL se definen como un arreglo de tuplas que contienen: * Nombre de la restricción * Restricción SQL a aplicar * El mensaje de error a ser desplegado en caso de que se viole la restricción
Si la base de datos ya tiene datos que violan la restricción, esta no va a crearse, ni aplicarse. Cuando esto pase, va a aparecer un error en el log del servidor como el siguiente: 2015-01-20 13:34:29,959 10290 INFO nombre_basedatos openerp.modules.module: module biblioteca: creating or updating database tables 2015-01-20 13:34:30,009 10290 WARNING nombre_basedatos openerp.models.schema: Table 'biblioteca_libro': unable to add 'unique(isbn)' constraint ! If you want to have it, you should update the records and execute manually: ALTER TABLE "biblioteca_libro" ADD CONSTRAINT "biblioteca_libro_unique_isbn" unique(isbn) Para aplicar la restricción SQL ud debe corregir los datos primero, para este caso eliminando los ISBN duplicados y actualizar nuevamente el módulo. Ejercicios propuestos --------------------Utilizando el código de la lección: - Crear un nuevo registro y verificar que los campos `active`, `fecha_publicacion` y `precio` se llenan con el valor por defecto indicado en el código. - Modificar el código para que el campo `fecha_compra` tenga un valor por defecto. - Modificar el código para que el valor por defecto del campo `state` sea *Solicitud* - Modificar el código para que el campo `nombre_autor` tenga un valor por defecto generado aleatoriamente utilizando [generador de nombres en python] (https://pypi.python.org/pypi/names/) - Cambie el atributo `copy` del campo `name` a *False*, verifique que sucede cuando se utiliza la opción de *Más >> Duplicar* en la vista formulario de un registro de libro. Revise nuevamente dejando `copy` con el valor de *True* - Verifique que no puede crear/editar un libro y asignar un ISBN ya utilizado por otro libro. Ajuste los datos para poder activar la restricción si esta no fue aplicada. - Adicionar una restricción utilizando el decorador `@api.constraints` para que el campo `paginas` no acepte valores menores a 0 ni valores mayores a 5000 - Utilizando `_sql_constraints` adicione un constraint para que el campo `precio` no acepte valores negativos. Ver [sentencia SQL soportada por posgreSQL para CHECK] (http://www.postgresql.org/docs/9.4/static/ddl-constraints.html)
Lección 05: Dominios, Busquedas, Filtros y Agrupamientos ======================================================== [TOC] Dominios -------Los dominios en Odoo son utilizados para filtrar los registros a los que se tiene acceso en las vistas o en consultas dentro del código del Modelo. Estos dominios se pueden asimilar a las condiciones que se agregan en un WHERE en una sentencia SQL. Si para nuestro ejemplo de la biblioteca normalmente en SQL ejecutamos SELECT * FROM biblioteca_libre WHERE paginas > 100 AND nombre_autor ILIKE '%cervantes%'
en Odoo el dominio se estructuraría [('paginas','>',100),('nombre_autor','ilike','%cervantes%')]` Un dominio es un listado de criterios, en el ejemplo anterior hay dos criterios, estos criterios estan formados por: - **`Nombre del campo`** del Modelo sobre el cual se aplica el filtro. - **`Operador`** a utilizarse para la búsqueda a realizar [=, !=, >, >=, <, <=, =?, =like, like, not like, ilike, not ilike, =ilike, in, not in, child_of] (https://www.odoo.com/documentation/8.0/reference/orm.html#domains) - **`Valor`**: Valor sobre el cual se compara para la búsqueda Los criterios se pueden combinar utilizando operadores lógicos en [notación prefijo o polaca](http://es.wikipedia.org/wiki/Notaci%C3%B3n_polaca) usando los operadores siguientes: - '&' AND, operador por defecto - '|' OR - '!' NOT Para una consulta SQL como esta: SELECT * FROM biblioteca_libro WHERE paginas > 100 AND nombre_autor ILIKE '%cervantes%' OR nombre_autor ILIKE '%marquez%' El dominio sería: [('paginas','>',100),'|',('nombre_autor','ilike','%cervantes%'), ('nombre_autor','ilike','%marquez%')] Búsquedas --------Las búsquedas (search) son un tipo de vista que se asocia a un Modelo con el objeto de permitir realizar búsquedas por campos especificos, adicionar filtros predeterminados y agrupaciones sobre los registros que se despliegan en las vistas tipo listado. A continuación un ejemplo de una vista search. biblioteca.libro.searchbiblioteca.libro
/>
domain="[('paginas','>=',1000)]" /> - **Búsquedas por campos**: Para permitir la búsqueda por campos especificos del modelo solo se debe agregar una etiqueta `field` e indicar el nombre del campo por el cual que se pemite realizar la búsqueda. En el momento de escribir en el cuadro de búsqueda que aparece en la parte superior derecha, se va a desplegar un listado de los campos sobre los cuales se va a aplicar la búsqueda. Estas búsquedas son abiertas, ud define el valor por el cual buscar. - **Filtros predeterminados**: estos son búsquedas predeterminadas que le usuario simplemente aplica haciendo clic sobre el filtro. Para adicionar un filtro necesita adicionar una etiqueta `filter` e indicar en el parámetro `domain` un dominio siguiendo la sintaxis vista en la sección anterior. Los filtros pueden agruparse utilizando la etiqueta `separator`, estos grupos sirven para indicar si al aplicar varios filtros se va a usar los operadores lógicos *AND* u *OR*. Si dos filtros aparecen en el mismo grupo se aplica *OR*, si estan en grupos aparte se aplica *AND*. Visualmente el cuadro de búsqueda en la interfaz web va a desplegar los filtros aplicados de manera separada si se usa un *AND* y unidos si se aplica *OR* tal como se muestra en la imagen a coninuación:  - **Agrupaciones**: Se pueden agrupar los registros por campos, estas agrupaciones son defidas con la etiqueta `filter` y el atributo `context` donde se incluye un diccionario con la clave `group_by` y el nombre del campo por el cual se va a agrupar. (Mayor información acerca de las vistas tipo búsqueda en Odoo) [https://www.odoo.com/documentation/8.0/reference/views.html#search] Ejercicios propuestos --------------------Tomando como base el código de la lección: 1. Cargue en el Odoo los datos del archivo [src/biblioteca/biblioteca_libro.csv] (src/biblioteca/biblioteca_libro.csv), esto lo puede hacer a través del link *_importar_* que aparece en la vista de listado del Modelo *biblioteca.libro*. [Más información acerca de importar archivos .csv] (https://www.odoo.com/documentation/8.0/reference/data.html#csv-data-files)
1. Utilizar las busquedas, filtros y agrupamientos ya programados para la lección. 1. Revise como se comportan los filtros de los grupos 'Estados' y 'Tamaños', luego del código de la vista elimine el separador ``, actualice el módulo y vea como se comportan los filtros ahora. 1. Selecciones varias opciones del formulario de búsqueda, luego use la opción llamada *Guardar Filtro Actual* y active la opción *Usar por defecto*. Haga click en el menuitem *Libros* para ver como siempre se aplica el filtro guardado. 1. Utilice la opción llamada *Búsqueda Avanzada* en el formulario de búsqueda. 1. Cree una vista de búsqueda para el Modelo *biblioteca.prestamo*
Lección 06: Vistas Dinámicas ============================ [TOC] Actualizar formulario al cambiar valores: @api.onchange -------------------------------------------------------Muchas veces es necesario que algunos campos del formulario se actualicen basados en las selecciones o datos que se ingresan en otros campos del formulario, para esto se utiliza el decorador `@api.onchange` en cualquier método python que se implemente en el Modelo. Ejemplo: ```python from openerp import models, fields, api class biblioteca_libro(models.Model): _name = 'biblioteca.libro' @api.onchange('precio') def onchange_precio(self): if self.precio and self.precio > 1000: self.descripcion = 'Ta muy caro el libro' ``` De igual forma se puede utilizar un método onchange para enviarle al usuario mensajes de validación a medida que va llenando el formulario, esto no es un remplazo para las restricciones que se adicionen con _sql_constraints o @api.constraints, ya que onchange solo aplica a nivel de vista y no se llama en el momento de guardar los datos. ```python from openerp import models, fields, api class biblioteca_libro(models.Model): _name = 'biblioteca.libro' @api.onchange('isbn') def onchange_warning_isbn(self): if self.isbn and len(self.isbn) < 10: self.descripcion = 'Verifique el ISBN cumpla con la norma' return { 'warning': { 'title': "ISBN",
'message': "El largo del ISBN debe ser mayor o igual a 10
caracteres", }
}
``` Si desea que un metodo @api.constraints sea también llamado cuando el usuario cambia el valor en el formulario web puede hacerlo adicionando el @api.onchange decorator. Pero debe tener en cuenta que el error se llama utilizando `raise ValidationError` y no retornando un diccionario como en el ejemplo anterior. ```python from openerp import models, fields, api class biblioteca_libro(models.Model): _name = 'biblioteca.libro' @api.one @api.constrains('paginas') @api.onchange('paginas') def _check_paginas(self): if self.paginas < 0 or self.paginas > 5000: raise ValidationError("Un libro debe tener entre 0 y 5000 páginas") ``` Si no se desea que se llame el onchange en una vista en particular puede adicionar en la vista: Mayor información del uso de onchange en: - https://www.odoo.com/documentation/8.0/reference/orm.html#onchange-updating-uion-the-fly - https://www.odoo.com/documentation/8.0/howtos/backend.html#onchange Cambios basado en otros campos: attrs ------------------------------------La interfaz también puede cambiar dinámicamente utilizando el atributo **attrs** en las etiquetas `button`,`field`,`notebook`. El atributo *attrs* permite que se cambie los valores definidos para los campos en las opciones `invisible`, `required` y `readonly` de acuerdo a una regla de dominio que se cumpla. - **Invisible**: El atributo invisible permite mostrar u ocultar un campo en la vista, el valor `True` oculta el campo. Ejemplo: En el ejemplo se indica que el campo descripción se oculta cuando el estado es del libro es **De baja**. - **Required**: El atributo required permite indicar si el campo es o no obligatorio a nivel de la vista, esto no cambia el valor de required para el campo a nivel de modelo. Ejemplo:
En el ejemplo se indica que el campo *paginas* es obligatorio a nivel de vista si el estado del libro es **Proceso de Compra**. - **Readonly**: El atributo readonly permite indicar si el campo es o no de solo lectura a nivel de la vista. Ejemplo: En el ejemplo se indica que el campo titulo es unicamente de lectura en la vista cuando el estado del libro es **Catalogado**. Igualmente puede incluir todos los atributos en **attrs**. Ejemplo: El ejemplo anterior hace que el campo titulo no aparezca en el formulario cuando el *state* es *baja*; el campo va a ser obligatorio si el estado es *compra* y quedará en modo solo lectura cuando el estado es *catalogado* Igualmente si ud quiere en la vista utilizar los atributos `invisible`, `required` y `readonly` sin depender de una condición lo puede hacer como se muestra acontinuación: Ejercicios propuestos --------------------Utilizando el código de la lección: 1. Verifique que el precio no puede ser editable a través de la vista formulario del libro, pero si a través del menú editar precios. Verifique en la definición de la vista como se hizo. 1. Cambie un libro de estado y vea como se comporta la pestaña de *fechas* cuando esta en modo edición. 1. Ajuste los campos de *Editorial*, *Clasificación* y *Género* para que sean obligatorios cuando el estado del libro sea *Adquirido* 1. Ajuste los campos de *Editorial*, *Clasificación* y *Género* para que sean de solo lectura cuando el estado es *Catalogado* y *De baja*
Lección 07: Relaciones entre Modelos ==================================== [TOC] Las aplicaciones comunes requieren que los datos esten relacionados, veremos a continuación como manejar las relaciones entre Modelos, a través de nuevos tipos de campos one2many, many2one y many2many. Relaciones Many2one -------------------
Utilizando un campo de tipo Many2one creamos una relación directa entre el Modelo actual (donde se define el campo) y algún otro Modelo, este campo crea la llave foranea entre las tablas de dos Modelos. Su estructura básica es la siguiente: nombre_campo_id = fields.Many2one('Nombre del Modelo', 'Etiqueta', [...]) Los parámetros recibidos por este tipo de campo son: - **Nombre del Modelo**: Nombre del Modelo con el cual crear la [relación de llave foránea](http://www.postgresql.org/docs/9.4/static/tutorial-fk.html), ej. `biblioteca.libro`, `res.partner`, `res.users` - **Etiqueta**: Etiqueta a desplegarse en la vista web para este campo - **ondelete**: Indica a la base de datos como manejar la relación cuando el registro del modelo relacionado sea eliminado. Los valores disponibles son: `set null`, `restrict`, `cascade`; por defecto se usa `set null` - **domain**: Criterio que limita el listado de registros a ser listados para ser relacionados con el Modelo actual. ej. Ver solo los libros catalogados y no los que estan en compra. :white_up_pointing_index: Ud puede agregar el dominio como un *string* `domain="[('state', '=', 'catalogado')]"` o como una *lista de tuplas* `domain=[('state', '=', 'catalogado')]`, cuando se adiciona como string el dominio se evalua en el lado del cliente y puede usar nombres de campos despues del operador, pero cuando es una lista de tuplas se evalua en el lado del servidor y no permite el manejo de campos, solo de valores. [Mayor información del campo Many2one en la información oficial de referencia de Odoo] (https://www.odoo.com/documentation/8.0/reference/orm.html#openerp.fields.Many2one) Ejemplo: ```python from openerp import models, fields, api class biblioteca_prestamo(models.Model): _name = 'biblioteca.prestamo' _description = 'Registro de prestamo' libro_id = fields.Many2one('biblioteca.libro', 'Libro prestado', domain=[('state', '=', 'catalogado')]) ``` ### Despliegue en la vista Para que el campo se despliegue en la vista debe simplemente agregar en el documento XML el tag `field` con el nombre del campo, como se hace regularmente. Pero de manera adicional tiene la posibilidad de adicionar los siguientes atributos: - Atributo **`widget`**: permite indicar que se utilice `selection`, para que la el widget de selección sea mucho más simple que el que viene por defecto y no despliegue la opción de crear o editar. - Atributo **domain**: Permite a nivel de vista adicionar restriciones de campos a desplegar para la selección de registros a ser relacionados. Relaciones One2many -------------------
Un campo One2many permite que el Modelo actual (donde se define el campo) pueda acceder a todos los registros relacionados, este campo requiere que el Modelo relacionado tenga creado un campo tipo Many2one que haga referencia al Modelo actual. Su estructura es la siguiente: nombre_campo_ids = fields.One2many('Nombre del Modelo Relacionado', 'Nombre del campo que contine la relación', 'Etiqueta', [...]) Los parámetros recibidos por este campo son: - **Nombre del Modelo Relacionado**: Nombre del Modelo que contiene el campo Many2one que apunta a este Modelo. Ej. `biblioteca.prestamo` - **Nombre del campo que contine la relación**: Nombre del campo Many2one que existe en el Modelo relacionado. Ej. `libro_id` Por convención el nombre del campo se le adiciona el sufijo **_ids** [Mayor información del campo One2many en la información oficial de referencia de Odoo] (https://www.odoo.com/documentation/8.0/reference/orm.html#openerp.fields.One2many) Ejemplo: ```python from openerp import models, fields, api class biblioteca_libro(models.Model): _name = 'biblioteca.libro' _description = 'Libro de la biblioteca' prestamo_ids = fields.One2many('biblioteca.prestamo', 'libro_id', 'Prestamos realizados') ``` ### Despliegue en la vista Para que el campo se despliegue en la vista debe simplemente agregar en el documento XML el tag `field` con el nombre del campo, como se hace regularmente. ej ``. Pero de manera adicional puede indicar como desplegar los registros relacionados: - Atributo **`mode`**: Por defecto se despliegan los registros relacionados utilizando la vista tipo listado o `tree` del Modelo relacionado, pero se puede indicar si se desa utilizar una vista tipo [`graph`] (https://www.odoo.com/documentation/8.0/reference/views.html#graphs) o [`kanban`] (https://www.odoo.com/documentation/8.0/reference/views.html#kanban), estas vistas se explicarán en otra lección. Se puede definir la estructura de la vista a ser utilizada dentro de la misma etiqueta `field` tal como se muestra a continuación: Relaciones many2many
-------------------La relación many2many consiste en que un Modelo A puede tener relación con uno o varios registros del Modelo B y a su vez el Modelo B se puede relacionar con varios registros del Modelo A, a diferencia de one2many donde la cardinalidad es de muchos a uno. Para esto se requiere de una tabla intermedia que almacena la relación entre los Modelos, esta tabla es generada automáticamente y no se puede acceder como un Modelo independiente. Su estructura es la siguiente: nombre_campo_ids = fields.Many2many('Nombre Modelo del Relacionado', string='Etiqueta') Los parámetros recibidos por este campo son: - **Nombre del Modelo Relacionado**: Nombre del Modelo que contiene el campo Many2one que apunta a este Modelo. Ej. `biblioteca.prestamo` - **domain**: Criterio que limita el listado de registros a ser listados para ser relacionados con el Modelo actual. Por defecto Odoo genera el nombre de la tabla intermedia y de los campos relacionados, pero estos pueden ser indicados explicitamente utilizando - **relation**: Nombre de la tabla a utilizar para almacenar los registros de la relación - **column1**: Nombre de la columna en la tabla de relación que va a contener los IDs del **Modelo actual** (donde se esta definiendo el campo) - **column2**: Nombre de la columna en la tabla de relación que va a contener los IDs del **Modelo relacionado** Por convención el nombre del campo se le adiciona el sufijo **_ids** Ejemplo: ```python from openerp import models, fields, api class biblioteca_libro(models.Model): _name = 'biblioteca.libro' _description = 'Libro de la biblioteca' genero_ids = fields.Many2many('biblioteca.genero', string="Generos") class biblioteca_genero(models.Model): _name = 'biblioteca.genero' _description = 'Genero literario' libro_ids = fields.Many2many('biblioteca.libro', string="Libros") ``` :white_up_pointing_index: El ejemplo va a crear una tabla para la relación llamada `biblioteca_genero_biblioteca_libro_rel` y con los campos: `biblioteca_libro_id` y `biblioteca_genero_id` ### Despliegue en la vista Para que el campo se despliegue en la vista debe simplemente agregar en el documento XML el tag `field` con el nombre del campo, como se hace regularmente. ej ``. Pero de manera adicional puede indicar como desplegar
los registros relacionados: - Atributo **`widget`**: Por defecto se despliegan los registros relacionados utilizando la vista tipo listado o `tree` del Modelo relacionado, pero se puede indicar si se desea utilizar un widget diferente como **`many2many_tags`**. Ejercicios propuestos --------------------Utilizando el código de ejemplo de la lección: 1. Crear un prestamo y ver como el campo libro solo muestra los libros en estado *catalogado*. 1. Adicione generos a un registro del Modelo libro. 1. En la vista tipo formulario del Modelo *prestamo* adicionar el atributo `widget="selection"` en el campo `libro_id`. Nota la diferencia? 1. En la vista tipo formulario del Modelo *libro* adicionar el atributo `widget="many2many_tags"` en el campo `genero_ids`. Nota la diferencia? Adicione nuevos generos a varios registros del Modelo libro. 1. En la vista tipo formulario del Modelo *libro* adicionar en el listado de prestamos el campo `duracion_dias`. 1. Crear un Modelo **biblioteca.editorial** con el campo `name` 1. Crear una relación many2one de *libro* a *editorial* y el inverso one2many de *libro* a *editorial*, desplegarlos en las vista formulario. 1. Crear un Modelo **biblioteca.autor** con el campo `name` 1. Crear una relación many2many de *libro* a *autor* y el inverso de *autor* a *libro*, desplegarlos en las vista formulario. 1. Verifique a través de pgadmin3 la estructura de las tablas y los *constraints* creados.
Lección 08: Campos funcionales, atributos y decoradores avanzados ================================================================= [TOC] Campos Related -------------En algunos casos es necesario tener disponible el valor de un campo de un Modelo relacionado, como si fuera un campo del Modelo actual, por ejemplo si tenemos los Modelos Persona -> Ciudad -> Pais, podemos adicionar un campo en Persona que nos diga directamente cual es el Pais, basado en la selección de la Ciudad. Para esto definimos un proxy a través de un campo con el atributo `related`. ```python from openerp import models, fields, api class pais(models.Model): _name = 'mi_modulo.pais' _description = 'Pais' name = fields.Char('Nombre') class ciudad(models.Model): _name = 'mi_modulo.ciudad'
_description = 'Ciudad' name = fields.Char('Nombre') pais_id = fields.Many2one('mi_modulo.pais','País') class persona(models.Model): _name = 'mi_modulo.persona' _description = 'Persona' ciudad_id = fields.Many2one('mi_modulo.ciudad', 'Ciudad de Residencia') pais = fields.Many2one(related='ciudad_id.pais_id') ``` Ud puede modificar el campo `persona->pais_id` y eso cambiará el valor en el registro `ciudad->pais_id`, también automáticamente la interfaz web va a mostrar el país cuando se selecciones la ciudad. Se pueden adicionar otros atributos propios del campo que aplicarian solo al Modelo actual (persona) tales como: string, help, readonly, required y estos no afectarían la definición original del campo. ` Campos computados ----------------Muchas veces los campos tienen valores que deben calcularse de automáticamente, para esto se utiliza el atributo: `compute` y el nombre del método del Modelo que hará el cálculo del valor. Por ejemplo: ```python from openerp import models, fields, api import datetime.timedelta class biblioteca_prestamo(models.Model): _name = 'biblioteca.prestamo' _description = 'Informacion de prestamo de libros' fecha = fields.Datetime( 'Fecha del Prestamo', help="Fecha en la que se presta el libro", ) duracion_dias = fields.Integer( 'Duración del Prestamo(días)', help="Número días por los cuales se presta el libro", ) fecha_devolucion = fields.Datetime( 'Fecha Devolución', compute='_compute_fecha_devolucion', help="Fecha de devolución del libro", ) @api.one def _compute_fecha_devolucion(self): """Calcula la fecha de devolución basado en la fecha inicial y la duración en días del prestamo""" fecha = fields.Datetime.from_string(self.fecha) self.fecha_devolucion = fecha + timedelta(days=10) ```
Con el código anterior el cálculo se va a realizar cuando se guarde el registro en la base de datos y en la interfaz el campo se va a mostrar como de solo lectura. Si se desea que se pueda también definir manualmente la fecha de devolución, se debe usar el atributo `inverse` y el nombre del método del Modelo que hará el cálculo inverso. Para nuestro ejemplo el método inverson debería calcular la duración en días a partir de la fecha del prestamo y la fecha de devolución. ```python from openerp import models, fields, api import datetime.timedelta class biblioteca_prestamo(models.Model): _name = 'biblioteca.prestamo' [...] fecha_devolucion = fields.Datetime( 'Fecha Devolución', compute='_compute_fecha_devolucion', inverse='_compute_inv_fecha_devolucion', help="Fecha de devolución del libro", ) [...] @api.one def _compute_inv_fecha_devolucion(self): """Calcula la fecha duración en días del prestamo basado en la fecha de devolución""" if self.fecha and self.fecha_devolucion: fecha = fields.Datetime.from_string(self.fecha) fecha_devolucion = fields.Datetime.from_string(self.fecha_devolucion) delta = fecha_devolucion - fecha self.duracion_dias = delta.days ``` Por defecto el valor del campo es calculado en el momento en que se carga el registro y no es almacenado en la base de datos, si se desea que se almacene en la base de datos se debe adicionar el atributo `store=True`. :happy_person_raising_one_hand: Cuando se utiliza store=True se recomienda utilizar el decorador depends, para que que se le indique cuando debe ser recalculado el valor del campo basado en los valores de otros campos del modelo, más detalles en la siguiente sección. Decorador: depends [email protected]('fecha', duracion_dias') Decorador: multi ---------------Decorador: one -------------Decorador: model ----------------
Atributo: states ---------------Manejo de árboles ------------------
related depends multi states parent
Related ------Los campos related se utiliza cuando el campo es una referencia de un id de otra tabla. Esto es para hacer referencia a la relación de una relación. **Estructura de definición** 'nombre_campo_related': fields.related ( 'id_busqueda', 'campo_busqueda', type = "tipo_campo", relation = "objeto_negocio_a_relacionar", string = "Texto del campo", store = False ) * **id_busqueda**: nombre de campo id de referencia del objeto de negocio relacionado para realizar la búsqueda. * **campo_busqueda**: nombre del campo de referencia, el cual devuelve el valor de ese campo según la busqueda realizada por el id_busqueda en el objeto de negocio a relacionar. * **type**: Tipo del campo related. Tipo de relación para el campo related con el objeto de negocio. * **relation**: Nombre del objeto de negocio al cual se aplica la relación con el campo tipo related. * **string**: Texto para el campo tipo related. * **store**: Este puede ser definida como True o False, segun lo requerido, con este valor estamos indicando si el valor devuelto por campo_busqueda es almacenado o no en la base de datos. **Ejemplo de aplicación**: 'titulo': fields.related( 'libro_id','titulo', type="char", string="Titulo", store=False, readonly=True ), En este ejemplo se crea el campo titulo de tipo related el cual devuelve el titulo del libro según el valor del campo libro_id. Domain ------
El atributo domain es un atributo aplicado a los campos para definir una restricción de dominio en un campo relacional, su valor por defecto es vacio. **Estructura de definición** domain = [('campo', 'operador_comparacion', valor)]) * **campo**: nombre del campo del objeto de negocio, este campo será utilizado para la restricción. * **operador_comparacion**: operador para comparar el campo con el valor establecido. * **valor**: valor asignado para evaluar la restricción de dominio. **Ejemplo de aplicación**: Function -------Un campo funcional es un campo cuyo valor se calcula mediante una función, en lugar de ser almacenado en la base de datos. **Estructura de definición** 'nombre_campo_function' : fields.function( _nombre_metodo, type='tipo_campo', obj= "objeto_de_negocio.function", method= True, string= "Texto del campo" ) * **_nombre_metodo**: nombre del método creado para realizar el calculo. * **type**: Tipo del campo funtion. Tipo de relación para el campo function con el objeto de negocio obj. * **obj**: objeto de negocio al cual se aplica la relacion con el campo function. * **method**: True. * **string**: Texto para el campo tipo function. **Ejemplo de aplicación**: Ejercicios propuestos --------------------1. Revisar los cambios realizados en el módulo ejemplo biblioteca. 1. Adicionar un campo tipo function.
Añadir un botón a la vista formulario -------------------------------------Se puede adicionar botones de acción a la vista formulario, los botones de acción permiten al usuario realizar diversas acciones en el registro actual. Maneja los siguientes atributos: Identificador del botón, que se utiliza para indicar qué método debe ser llamado, que envió la señal o que la acción ejecutada. * ***name***: Nombre de la función que se activiara por medio del boton. Identificador del botón, se utiliza para indicar qué método debe ser llamado, que señal o que acción se ejecutará. * ***string***: Etiqueta que se mostrará en el botón. * ***type***: Define el tipo de acción realizada cuando se activa el botón. * *workflow* (default): El botón enviará una señal de workflow al modelo actual con el Name del botón como señal del workflow y retornará como parámetro el ID del registro (en una lista). La señal del workflow puede retornar una acción la cual será ejecutada, de lo contrario devolverá False. * *object*: El botón ejecutará el método de nombre Name en el modelo actual, retornará como parámetro el ID del registro (en una lista). Retorna una acción la cual será ejecutada. * action: El botón activará la ejecución de una acción (ir.actions.actions). El ID de esta acción es el Name del botón. * ***special***: Sólo tiene un valor *cancelar*, lo que indica que el popup debe cerrarse sin realizar ninguna llamada RPC o acción. Se usa en ventanas popup-type, por ejemplo wizard.El atributo special y type son incompatibles. * ***confirm***: Ventana de confirmación antes de ejecutar la tarea del botón. * ***icon***: Mostrar un icono en el botón. * ***states, attrs, invisible***: Significado estándar para esos atributos de vista. * ***default_focus***: Si se establece en un valor Truthy, automáticamente se selecciona el botón, tambien usado si se presiona RETURN en la vista formulario. **Estructura para adicionar un botón en la vista** Ejemplo de adicion de un botón: Método Search ------------Este método realiza la búsqueda de regristros, basándose en el dominio de búsqueda indicado. Maneja los siguientes parámetros: * ***offset***: Número de registros a omitir. Parámetro opcional, por defecto 0. * ***limit***: Número máximo de registros a devolver. Parámetro opcional, por defecto None. * ***order***: Columnas para establecer el criterio de ordenación. Opcional. Por defecto self._order=id. * ***count***: Devuelve sólo el número de registros que coinciden con el criterio de búsqueda. * ***args***: Lista de tuplas que especifican el dominio de búsqueda. Cada tupla de la lista del dominio de búsqueda necesita 3 elementos en la forma ('field_name',
'operator', value), donde: * *field_name*: Nombre del campo del objeto de negocio * *operator*: Operador de comparación (=, !=, >, >=, <,<=, like, ilike, in, not in, child_of, parent_left, parent_right). * *value*: Valor con que realizar la comparación. El dominio de búsqueda puede ser una combinación que use 3 operadores lógicos que pueden añadirse entre tuplas: * '&': Y lógico. Por defecto,se omite el operador. * '|': O lógico. * '!': No lógico o negación. **Estructura de definición del método** search(cr, uid, args, offset=0, limit=None, order=None, context=None, count=False) Ejemplo: libros_pool = self.pool.get('biblioteca.libro') libros_ids = libros_pool.search(cr, uid,[('state','=','compra')], context=context) En este ejemplo el método search realiza una búsqueda por todos los registros del objeto *biblioteca.libro* y cuando un registro coincida con el criterio de búsqueda 'state','=','compra', el id del registro se almacenará en libros_ids. Método Read ----------Obtiene una lista de los valores de los campos fields de los registros ids, devuelve un diccionario con el nombre y valor de los campos solicitados. Tiene los siguientes parámetros: * ***fields***: Lista de campos. **Estructura de definición del método** read (cr, uid, ids, fields=None, context=None) Ejemplo de aplicación read: libros_pool = self.pool.get('biblioteca.libro') libros_ids = libros_pool.search(cr, uid,[('state','=','compra')], context=context) libros_records = libros_pool.read(cr, uid, libros_ids, ['titulo','autor']) En este ejemplo del método read obtiene una lista de los valores de los campos *'titulo', 'autor'* de los registros que coincidan con los ids almacenados en libros_ids, esta lista se almacena en libros_records como un diccionario con nombre y valor de los campos indicados. Ejemplo de aplicación del método read: **Adición de función en el objeto de negocio biblioteca.libro_prestamo** def obtener_promedio_prestamo_metodo_read(self,cr,uid,ids,context=None): prestamo_ids = self.read(cr, uid, ids, ['prestamo_ids'], context=context)
prestamo_pool = self.pool.get('biblioteca.libro_prestamo') prestamo_records = prestamo_pool.read(cr, uid, prestamo_ids[0] ['prestamo_ids'], ['fecha_prestamo', 'fecha_regreso'], context=context) tiempo_total = 0 for record in prestamo_records: tiempo_dias = (datetime.strptime(record['fecha_regreso'],'%Y-%m-%d') datetime.strptime(record['fecha_prestamo'],'%Y-%m-%d')).days tiempo_total = tiempo_total + tiempo_dias raise osv.except_osv('Promedio de tiempo de préstamo','{0} días'.format(tiempo_total)) return True **Adición de boton en la vista formulario del objeto de negocio biblioteca.libro_prestamo** En este ejemplo de aplicación se realiza una búsqueda en los registros de préstamos que correspondan al estado entregado de los libros con id libro_id, se realiza un cálculo del promedio de tiempo de préstamo del libro. record_ids solo almacena una lista con los campos 'fecha_prestamo' y 'fecha_regreso' por cada registro encontrado. Método Browse ------------El método browse obtiene registros como objetos permitiendo utilizar la notación de puntos para explorar los campos y las relaciones, permite consultar con facilidad campos relacionados de forma encadenada a partir de un objeto. **Estructura de definición del método** browse (cr, uid, ids, context=None) Ejemplo: libros_pool = self.pool.get('biblioteca.libro') libros_ids = libros_pool.search(cr, uid,[('state','=','compra')], context=context) libros_records = libros_pool.browse(cr, uid, libros_ids, context=context) En este ejemplo el método browse obtiene los registros que correspondan a los ids almacenados en libros_ids, estos registros se almacenan en libros_records. Ejemplo de aplicación del método browse: **Adición de función en el objeto de negocio biblioteca.libro_prestamo** def obtener_promedio_prestamo_metodo_browse(self,cr,uid,ids,context=None): prestamo_ids = self.read(cr, uid, ids, ['prestamo_ids'], context=context) prestamo_pool = self.pool.get('biblioteca.libro_prestamo') prestamo_records = prestamo_pool.browse(cr, uid, prestamo_ids[0] ['prestamo_ids'], context=context) tiempo_total = 0 for record in prestamo_records: tiempo_dias = (datetime.strptime(record['fecha_regreso'],'%Y-%m-%d') datetime.strptime(record['fecha_prestamo'],'%Y-%m-%d')).days tiempo_total = tiempo_total + tiempo_dias
raise osv.except_osv('Promedio de tiempo de préstamo','{0} días'.format(tiempo_total)) return True **Adición de boton en la vista formulario del objeto de negocio biblioteca.libro_prestamo** Este ejemplo realiza la misma tarea que la función obtener_promedio_prestamo_metodo_read, con la diferencia que record_ids almacena el registro completo de los prestamo_ids encontrados. Ejercicios propuestos --------------------1. Adicionar una restricción al préstamo por libros. Solo debe existir un préstamo activo por cada libro.
[[ title: Lección 10: Extensión de Módulos Base author: STRT Grupo I+D+I ]] Lección 10: Extensión de Módulos Base ===================================== [TOC] Extender Objetos ---------------Los objetos de negocio pueden heredar de un módulo base, esta herencia se puede utilizar para modificar, extender, utilizar métodos. **Estructura de definición** _inherit = 'nombre_objeto_base_a_heredar' **_inherit**: Se utiliza cuando heredamos Modelos o Clases en OpenERP. **_inherits**: La lista de objetos base que el objeto hereda. Esta lista es un diccionario de la forma: {'name_of_the_parent_object': 'name_of_the_field', ...}. Existen dos formas de extender un objeto. Ambas formas generan una nueva clase, que tiene campos y funciones heredadas, así como campos y métodos adicionales. * **Forma 1**: Las instancias de estas clases son visibles por las vistas (views or trees) que operan con la clase padre. Este tipo de herencia se denomina herencia de clase (class inheritance), es de utilidad para sobreescribir métodos de la clase padre. Ejemplo: class nombre_clase(osv.osv): _name = 'nombre_objeto_base'
_inherit = 'nombre_objeto_base' _columns = { 'campo_1: fields.tipo_campo('text_campo'), } nombre_clase() * **Forma 2**: Las instancias de estas clases no son visibles por las vistas (views or trees) que operan con la clase padre. Las instancias de estas clases contienen todos las propiedades y métodos de la instancia padre junto con las propiedades y métodos definidos en la nueva entidad. Este tipo de herencia se denomina herencia por prototipos (inheritance by prototyping). La nueva subclase contiene una copia de las propiedades de la clase padre. Ejemplo: class nombre_clase(osv.osv): _name = 'nombre_objeto_negocio' _inherit = 'nombre_objeto_base' _columns = { 'campo_1: fields.tipo_campo('text_campo'), } nombre_clase() **Ejemplo de aplicación**: class res_users(osv.osv): _inherit = "res.users" _columns = { 'prestamo_id' : fields.one2many('biblioteca.libro_prestamo', 'user_id', 'Préstamos'), } res_users() Se adiciona el campo 'user_id' al objeto biblioteca.libro_prestamo: 'user_id': fields.many2one('res.users', 'Usuario solicitante', help= 'Usuario que solicita el préstamo' ), Con este ejemplo relacionamos el objeto de negocio res.users con los préstamos de los libros. Se extieden el objeto res.users y se le adiciona el campo prestamo_id. Extender Vistas --------------También podemos heredar vistas, al igual que se puede heredar objetos. Parámetros: * ***inherid_id***: ID de la vista ha heredar. La ' es la primera carpeta que se encuentra en ADDONS, en la cuál se encuentra el fichero xml, en el que está definida la vista. **Estructura de definición** nombre.form.inheritnombreform
id es el nuevo identificador de la nueva vista. Por definición manejar el nombre del id terminado en 'form_view_inh' para identificar que es una vista heredada. **Ejemplo de aplicación**: res.users.form.inheritres.usersform En este ejemplo se extiende la vista base.view_users_form y se adiciona el nuevo campo prestamo_id. Ejercicios propuestos --------------------1. Revisar la extensión del objeto y la vista realizada en la lección. 1. Consultar los préstamos realizados por los usuarios, igresando al menú usuarios ubicado en configuración, pestaña detalles adicionales. Lección 11: Otros Widgets y Vistas ================================== [TOC] Widgets ------Widget es la clase base para todos los componentes visuales, un widget es un
componente genérico dedicado a mostrar el contenido al usuario. Los widgets son utilizados de manera automática de acuerdo al tipo de dato del campo a mostrar, pero estos widgets pueden ser manualmente seleccionados si es necesario. Existen diferentes widgets que pueden seleccionarse: **widget="many2many_tags"**: Permite la selección para un campo Many2many al estilo de tags de la web 2.0 **widget="monetary"**: Permite visualizar el simbolo moneda, requiere que en el atributo `options` se indique el campo de tipo *res.currency* (tipo de moneda) a ser utilizado. **widget="mail_followers"**: Permite adicionar seguidores a las notificaciones de un registro, el Modelo debe heredar de `mail.thread` **widget="mail_thread"**: Despliega las notificaciones que tiene un registro, el Modelo debe heredar de `mail.thread` **widget="statusbar"**: Muestra los valores de campo Selection en una barra, señalando el que esta actualmente seleccionado. En el widget se puede indicar por defecto cuales valores desplegar `statusbar_visible` y si es clickable para poder hacer cambiar la selección actual `clickable="1"`. Las opciones statusbar_visible y clickable son mutuamente excluyentes. **widget="progressbar"**: Muestra el valor del campo en forma de una barra de progreso **widget="url"**: Muestra el campo como un enlace web **widget="integer"**: Permite almacenar solo valores enteros **widget="image"**: Muestra el valor de un campo binary como una imagen **widget="handle"**: Permite organizar un listado de registros y almacenar la posición en un campo con el nombre **sequence**. Para que los listados se desplieguen utilizando el campo sequences se debe adicionar en el Módelo el atributo _order="sequence ASC" **widget="selection"**: Despliega un campo Many2one sin las opciones de buscar, crear o editar. Ejemplo de aplicación de widget: Vista tipo kanban ----------------Las vistas tipo kanban permiten manejar información importante (campos principales) en imágenes o tarjetas, es util para poder identificar los registros de una forma más rápida y así agilizar la consulta sobre los datos. Ejemplo para la creación de una vista tipo kanban: libro.kanbanbiblioteca.librokanban
Para que la vista se despliegue es necesario adicionar en el action_libro el tipo de vista kanban. Librobiblioteca.libroformtree,form,graph,kanban Vista tipo gantt ---------------Las vistas tipo gantt permiten mostrar el tiempo planificado o transcurrido para el desarrollo de tareas o actividades, es una vista dinámica, se puede hacer click en cualquier parte del gráfico, arrastrar y soltar el objeto en otra ubicación. Ejemplo para la creación de una vista tipo gantt: biblioteca.libro_prestamo.ganttbiblioteca.libro_prestamo
Para que la vista se despliegue es necesario adicionar en el action_libro_prestamo el tipo de vista gantt. Prestamobiblioteca.libro_prestamoformtree,form,gantt Vista tipo calendar ------------------Las vistas tipo calendar permiten visualizar la planificación en tiempo para el desarrollo de tareas o actividades, es una vista dinámica, se puede hacer click en cualquier parte del gráfico, arrastrar y soltar el objeto en otra ubicación. Ejemplo para la creación de una vista tipo calendar: biblioteca.libro_prestamo.calendarbiblioteca.libro_prestamo Para que la vista se despliegue es necesario adicionar en el action_libro_prestamo el tipo de vista calendar. Prestamobiblioteca.libro_prestamoformtree,form,gantt,calendar Vista tipo Gráfica -----------------Esta vista permite desplegar los datos disponibles en una grafica. Cada vista gráfica puede ser definida utilizando los siguientes elementos básicos: * atributo **`type`**: Indica el tipo de gráfica a utilizarse (pie, bar), por defecto pie * atributo **`orientation`**: Indica la orientación de las barras (horizontal, vertical) * **`field`**: Se debe ingresar como mínimo dos campos field (eje X, eje Y, eje Z), un tercero es opcional 3 * atributo **`group`**: Se coloca en 1 para el campo a ser utilizado en el GROUP BY * atributo **`operator`**: Indica el tipo de operador de agregación a ser utilizado (+,*,**,min,max)
Ejemplo para la creación de una vista tipo gráfico: libro.graphbiblioteca.libro Botones ------- confirm - states - magic
@api.one def button_fecha_compra_hoy(self): self.fecha_compra = fields.Date.today() self.state = 'adquirido' Ejercicios propuestos --------------------1. Verificar las diferentes vistas creadas. 1. Coloque el libro en estado *Proceso de compra* y haga click en el botón *Comprado hoy*, vea como cambian los campos *fecha de compra* y el *estado* del libro. 1. Adicioné un botón llamado *Devolver compra* que aparezca cuando el estado sea *Adquirido* y que cambie la fecha de compra a vacio y el estado a *Proceso de Compra*
[[ title: LecciOn 13: Campos y Atributos Avanzados author: STRT Grupo I+D+I ]] LecciOn 13: Workflow ==================== [TOC] ¿Que es Workflow? ----------------Los Workflow nos permiten gestionar las actividades relacionadas a los objetos de negocio, se representan como grafos dirigidos donde los nodos son las actividades y los conectores son las transiciones. La actividades son las tareas a realizar y las transiciones indica como el workflow cambia de una actividad a otra con el fin de gestionar los ciclos de vida de los objetos. CreaciOn de Workflow -------------------En la definiciOn de un Workflow, se pueden indicar condiciones, señales, y transiciones, por lo que el comportamiento del workflow depende de las acciones realizadas por usuario sobre el objeto de negocio. Lo primero a tener en cuenta es que el Worklow es definido en objetos de negocio con campo 'state'. * **id**: id como identificador del workflow. Se define como “wkf_†+objeto_de_negocio * **model**: Se define como â€workflowâ€. * **Name**: Nombre para el Workflow. * **Osv**: Nombre del objeto de negocio al cual se le define el Workflow. * **on_create**: Crea el Workflow al crear el objeto.
Ejemplo de una declaraciOn del workflow: nombremodulo.basicobject.nameTrue Restricciones para Workflow --------------------------Seguridad para Workflow ----------------------Ejercicios propuestos --------------------1. Revisar el workflow definido para el objeto de negocio bliblioteca.libro del mOdulo ejemplo biblioteca. 1. Definir el workflow para el objeto de negocio biblioteca.libro_prestamo.