Está en la página 1de 43

Construcción de modulo en Odoo 10

Iniciar/detener el servidor Odoo


Odoo utiliza una arquitectura cliente / servidor en el que los clientes son los navegadores web que acceden al
servidor Odoo a través de RPC.
La lógica de negocio y la extensión se lleva a cabo generalmente en el lado del servidor, aunque apoyan las
funciones del cliente (por ejemplo, una nueva representación de datos tales como mapas interactivos) puede ser
añadido al cliente.
Con el fin de iniciar el servidor, basta con invocar el comando odoo-bin en la cáscara, la adición de la ruta completa al
archivo si es necesario:
odoo - bin
El servidor es detenido por golpear Ctrl-C dos veces desde el terminal, o matando el proceso OS correspondiente.

Construir un módulo Odoo


Ambas extensiones de servidor y cliente se empaquetan como módulos que se cargan opcionalmente en una base
de datos.
Los módulos Odoo puede agregar nueva lógica de negocio a un sistema, o modificar y ampliar la lógica de negocio
existente: un módulo puede ser creado para añadir reglas de contabilidad de su país para el apoyo de la contabilidad
general de Odoo, mientras que el siguiente módulo añade soporte para la visualización en tiempo real de una flota de
autobuses. Todo en Odoo por lo tanto comienza y termina con módulos.

Composición de un módulo
Un módulo Odoo puede contener una serie de elementos:
Objetos de negocio (Business objects)
Declarado como clases de Python, estos recursos se conservan automáticamente por Odoo basado en su
configuración
Archivos de información (Data files)
XML o CSV archivos de metadatos que declaran (vistas o flujos de trabajo), los datos de configuración (módulos
de parametrización), datos de demostración y más
Controladores web (Web controllers)
Manejar las solicitudes de los navegadores Web
Datos de la web estática (Static web data)
Imágenes, archivos CSS o Javascript empleado por la interfaz web o sitio web

Estructura del módulo


Cada módulo es un directorio dentro de un directorio de módulos. Los directorios módulo se especifican mediante el uso de la --
addons-path opción.
Tip
La mayoría de las opciones de línea de comandos también se pueden configurar mediante un archivo
de configuración.
Un módulo Odoo se declara por su manifiesta (manifest). Consulte la documentación de manifiesto al respecto.
Un módulo es también un paquete de Python con un archivo __init__.py, que contiene instrucciones de
importación para diversos archivos de Python en el módulo.
Por ejemplo, si el módulo tiene un solo mymodule.py archivo __init__.py puede contener:
from . import mymodule
Odoo proporciona un mecanismo para ayudar a configurar un nuevo módulo, odoo-bin tiene un
subcomando scaffold para crear un módulo vacío:

$ odoo-bin scaffold <module name> <where to put it>


El comando crea un subdirectorio para su módulo, y crea automáticamente un montón de archivos estándar para un
módulo. La mayoría de ellos simplemente contener código XML o comentado. El uso de la mayor parte de esos
archivos se explicará a lo largo de este tutorial.

Ejercicio
Creación de módulos
Utilice la línea de comandos anterior para crear un módulo vacío abierto Academia, e instalarlo en Odoo.

1. Invocar el comando odoo-bin scaffold openacademy addons.


2. Adaptar el archivo de manifiesto a su módulo.
3. No preocuparse por los demás archivos.

Mapeo objeto-relacional
Un componente clave de Odoo es la capa ORM. Esta capa evita tener que escribir la mayor parte de SQL con la
mano y proporciona servicios de extensibilidad y seguridad ( 2 ).
Los objetos de negocio se declaran como clases de Python que se extienden Model, que los integra en el sistema
automatizado de persistencia.
Los modelos pueden ser configurados mediante el establecimiento de una serie de atributos bajo su definición. El
atributo más importante es _name que se requiere y define el nombre para el modelo en el sistema Odoo. He aquí
una definición mínimamente completa de un modelo:
from odoo import models
class MinimalModel(models.Model):
_name = 'test.model'

Campos del modelo


Los campos que se utilizan para definir lo que el modelo puede almacenar y dónde. Los campos son definidos como
atributos en la clase del modelo:
from odoo import models, fields

class LessMinimalModel(models.Model):
_name = 'test.model2'

name = fields.Char()

Los atributos comunes


Al igual que el modelo en sí, sus campos se pueden configurar, mediante el paso de configuración de atributos como
parámetros:
name = field.Char(required=True)
Algunos atributos están disponibles en todos los campos, estos son los más comunes:
string( unicode, Por defecto: el nombre del campo)
La etiqueta del campo en la interfaz de usuario (visible por los usuarios).
required( bool, Por defecto: False)
Si el campo True no puede estar vacío, o bien debe tener un valor por defecto o que siempre se le da un
valor al crear un registro.
help( unicode, Por defecto: '')
Formato largo, proporciona una información sobre herramientas ayuda a los usuarios en la interfaz de
usuario.
index( bool, Por defecto: False)
Pide que Odoo crear un índice de base de datos en la columna.

Por defecto en Odoo se escribe default.


Campos simples
Hay dos amplias categorías de campos: campos "simples", que son valores atómicos almacenados directamente en
la mesa del modelo y campos "relacionales" vinculación de registros (del mismo modelo o de diferentes modelos).
Ejemplo de campos simples son Boolean, Date, Char.

Campos reservados
Odoo crea algunos campos en todos los modelos ( 1 ). Estos campos son gestionados por el sistema y no se deben
escribirse. Pueden ser leídos si útil o necesario:
id( Id)
El identificador único para un registro en su modelo.
create_date( Datetime)
Fecha de creación del registro.
create_uid( Many2one)
Usuario que creó el registro.
write_date( Datetime)
Fecha de la última modificación del registro.
write_uid( Many2one)
Usuario que modificó por última vez el registro.

Los campos especiales


Por defecto, Odoo también requiere un campo name en todos los modelos para diferentes comportamientos de
visualización y búsqueda. El campo que se utiliza para estos fines puede ser anulado por el ajuste _rec_name.

Ejercicio
Definir un modelo
Definir un nuevo modelo de datos curso en el módulo openacademy. Un curso tiene un título y una
descripción. Los cursos deben tener un título.
Editar el archivo openacademy/models/models.py para incluir un curso de clases.
openacademy/models.py
from odoo import models, fields, api

class Course(models.Model):
_name = 'openacademy.course'

name = fields.Char(string="Title", required=True)


description = fields.Text()

Archivos de información
Odoo es un sistema altamente impulsado por datos. Aunque el comportamiento se personaliza mediante código
Python, una parte del valor de un módulo se encuentra en los datos que establece al cargarse.
Tip Existen algunos módulos únicamente para añadir datos en Odoo

Los módulos de datos se declara a través de data files, archivos XML con elementos <record>. Cada
elemento <record> crea o actualiza un registro de base de datos.

<odoo>
<data>
<record model="{model name}" id="{record identifier}">
<field name="{a field name}">{a value}</field>
</record>
</data>
</odoo>
 model es el nombre del modelo Odoo para el registro.
 id es un identificador externo , permite referír al registro (sin necesidad de conocer su identificador en la base
de datos).
 <field> elementos tienen un name que es el nombre del campo en el modelo (por ejemplo description). Su
cuerpo es el valor del campo.

Los archivos de información tienen que ser declarados en el archivo de manifiesto para ser cargados, pueden ser
declaradas en la lista 'data' (siempre cargado) o en la lista 'demo' (sólo se cargan en modo de demostración).

Ejercicio
Definir los datos de demostración
Crear datos de demostración llenando el modelo de Cursos con unos cuantos cursos de demostración. Editar el
archivo openacademy/demo/demo.xml para incluir algunos datos.
openacademy/demo.xml
<odoo>
<data>
<record model="openacademy.course" id="course0">
<field name="name">Course 0</field>
<field name="description">Course 0's description

Can have multiple lines


</field>
</record>
<record model="openacademy.course" id="course1">
<field name="name">Course 1</field>
<!-- no description for this one -->
</record>
<record model="openacademy.course" id="course2">
<field name="name">Course 2</field>
<field name="description">Course 2's description</field>
</record>
</data>
</odoo>

Acciones y Menús
Acciones y menús son registros regulares en la base de datos, normalmente declarados a través de archivos de
datos. Las acciones pueden ser activadas de tres maneras:

1. haciendo clic en los elementos de menú (vinculado a acciones específicas)


2. haciendo clic en los botones en las vistas (si éstos están conectados a las acciones)
3. como acciones contextuales en objetos

Debido a que los menús son algo complejas, para declarar hay un acceso directo <menuitem> para declarar
una ir.ui.menu y conectarlo a la acción correspondiente con mayor facilidad.
<record model="ir.actions.act_window" id="action_list_ideas">
<field name="name">Ideas</field>
<field name="res_model">idea.idea</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_ideas" parent="menu_root" name="Ideas" sequence="10"
action="action_list_ideas"/>

Peligro
La acción debe ser declarado antes de su correspondiente menú en el archivo XML.
Los archivos de datos se ejecutan secuencialmente, el id de la acción debe estar presente en la base de datos
antes de que se puede crear el menú.

Ejercicio
Definir nuevas entradas de menú
Definir nuevas entradas de menú para acceder a los cursos bajo la entrada del menú OpenAcademy. Un usuario
debe ser capaz de:

 mostrar una lista de todos los cursos


 crear / modificar cursos

1. Crear openacademy/views/openacademy.xml con una acción y los menús que desencadenan la acción
2. Añadirlo a la lista data de openacademy/__manifest__.py

openacademy/__manifest__.py

'data': [
# 'security/ir.model.access.csv',
'templates.xml',
'views/openacademy.xml',
],
# only loaded in demonstration mode
'demo': [

openacademy/views/openacademy.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!-- window action -->
<!--
La siguiente etiqueta es una definición de acción para una "acción de ventana",
Que es una acción que abre una vista o un conjunto de vistas -->
<record model="ir.actions.act_window" id="course_list_action">
<field name="name">Courses</field>
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">Create the first course
</p>
</field>
</record>

<!--
Menú de nivel superior: sin padre -->
<menuitem id="main_openacademy_menu" name="Open Academy"/>
<!-- Se necesita un primer nivel en el menú del lado izquierdo
Antes de usar la acción = atributo -->
<menuitem id="openacademy_menu" name="Open Academy"
parent="main_openacademy_menu"/>
<!-- El menú siguiente debe aparecer * después de *
Su padre openacademy_menu y * después de su *
Acción course_list_action -->
<menuitem id="courses_menu" name="Courses" parent="openacademy_menu"
action="course_list_action"/>
<!--
Ubicación completa de la identificación:
Action = "openacademy.course_list_action"
No es necesario cuando es el mismo módulo -->
</data>
</odoo>

Basic views
Las vistas definen la forma en que se muestran los registros de un modelo. Cada tipo de vista representa un modo de
visualización (una lista de registros, un gráfico de su agregación, ...). Las vistas pueden ser solicitados ya sea de
forma genérica a través de su tipo (por ejemplo, una lista de socios ) o específicamente a través de su ID. Para
solicitudes genéricas, se utilizará la vista con el tipo correcto y la prioridad más baja (lo que la vista de menor
prioridad de cada tipo es la vista por defecto para ese tipo).
View inheritance permite alterar vistas declarados en otra parte (la adición o eliminación del contenido).
Generic view declaration
Una vista es declarado como un registro del modelo ir.ui.view. El tipo de vista está implícito en el elemento raíz
del campo arch:
<record model="ir.ui.view" id="view_id">
<field name="name">view.name</field>
<field name="model">object_name</field>
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<!-- view content: <form>, <tree>, <graph>, ... -->
</field>
</record>
Peligro
El contenido de la vista es XML. El campo arch por lo tanto debe ser declarado como type="xml" para ser
analizado correctamente.

Tree views
Vistas de árbol, también llamadas vistas de listas, registros de visualización en forma de tabla.
Su elemento raíz es <tree>. La forma más simple de la vista de árbol se limita a enumerar todos los campos a
mostrar en la tabla (cada campo como una columna):
<tree string="Idea list">
<field name="name"/>
<field name="inventor_id"/>
</tree>

Form views
Los formularios se utilizan para crear y editar registros individuales.
Su elemento raíz es <form>. Se componen de elementos de alto nivel de la estructura (grupos, cuadernos) y
elementos interactivos (botones y campos):
<form string="Idea form">
<group colspan="4">
<group colspan="2" col="2">
<separator string="General stuff" colspan="2"/>
<field name="name"/>
<field name="inventor_id"/>
</group>

<group colspan="2" col="2">


<separator string="Dates" colspan="2"/>
<field name="active"/>
<field name="invent_date" readonly="1"/>
</group>

<notebook colspan="4">
<page string="Description">
<field name="description" nolabel="1"/>
</page>
</notebook>

<field name="state"/>
</group>
</form>

Exercise
Personaliza vista form usando XML
Create your own form view for the Course object. Data displayed should be: the name and the description of the
course.
openacademy/views/openacademy.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record model="ir.ui.view" id="course_form_view">
<field name="name">course.form</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<form string="Course Form">
<sheet>
<group>
<field name="name"/>
<field name="description"/>
</group>
</sheet>
</form>
</field>
</record>

<!-- window action -->


<!--
The following tag is an action definition for a "window action",

Ejercicio
NOTEBOOK
En la vista Formulario de curso, coloque el campo de descripción bajo una pestaña, de manera que sea más fácil
agregar otras pestañas más adelante, con información adicional.
Modificar el formulario curso de la siguiente manera:
openacademy/views/openacademy.xml
<sheet>
<group>
<field name="name"/>
</group>
<notebook>
<page string="Description">
<field name="description"/>
</page>
<page string="About">
This is an example of notebooks
</page>
</notebook>
</sheet>
</form>
</field>

Las vistas formulario también pueden usar plano HTML para los diseños más flexibles:
<form string="Idea Form">
<header>
<button string="Confirm" type="object" name="action_confirm"
states="draft" class="oe_highlight" />
<button string="Mark as done" type="object" name="action_done"
states="confirmed" class="oe_highlight"/>
<button string="Reset to draft" type="object" name="action_draft"
states="confirmed,done" />
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Idea Name" />
<h1><field name="name" /></h1>
</div>
<separator string="General" colspan="2" />
<group colspan="2" col="2">
<field name="description" placeholder="Idea description..." />
</group>
</sheet>
</form>

Search views
Las Vistas de búsqueda personalizar el campo de búsqueda asociado a la vista de la lista (y otras vistas
agregadas). Su elemento raíz es <search> y que están compuesto de campos que definen los campos que se
pueden buscar en:
<search>
<field name="name"/>
<field name="inventor_id"/>
</search>

Si no existe Buscar en la vista para el modelo, Odoo genera uno que sólo permite la búsqueda en el campo name.

Ejercicio
Buscar cursos
Permitir la búsqueda de cursos en función de su título o su descripción.
openacademy/views/openacademy.xml
</field>
</record>

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


<field name="name">course.search</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="description"/>
</search>
</field>
</record>

<!-- window action -->


<!--
The following tag is an action definition for a "window action",

Las relaciones entre los modelos


Un registro de un modelo puede estar relacionado con un registro de otro modelo. Por ejemplo, un registro de orden
de venta se relaciona con un registro de cliente que contiene los datos del cliente; También se relaciona con sus
registros de línea de orden de venta.

Ejercicio
Crear un modelo de sesión
Para el módulo abierto Academia, consideramos un modelo para sesiones: una sesión es una ocurrencia de un
curso impartido en un momento dado para un público determinado.
Crear un modelo para las sesiones. Una sesión tiene un nombre, una fecha de inicio, una duración y un número de
asientos. Añadir una acción y un elemento de menú para mostrarlos. Hacer que el nuevo modelo visible a través de
un elemento de menú.

1. Crear la clase de sesión en openacademy/models/models.py.


2. Añadir el acceso al objeto de sesión en openacademy/view/openacademy.xml.

openacademy/models.py
name = fields.Char(string="Title", required=True)
description = fields.Text()

class Session(models.Model):
_name = 'openacademy.session'

name = fields.Char(required=True)
start_date = fields.Date()
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")

openacademy/views/openacademy.xml
<!-- Full id location:
action="openacademy.course_list_action"
It is not required when it is the same module -->

<!-- session form view -->


<record model="ir.ui.view" id="session_form_view">
<field name="name">session.form</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<form string="Session Form">
<sheet>
<group>
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
</group>
</sheet>
</form>
</field>
</record>

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


<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>

<menuitem id="session_menu" name="Sessions"


parent="openacademy_menu"
action="session_list_action"/>
</data>
</odoo>

Nota
digits=(6, 2) especifica la precisión de un número float: 6 es el número total de dígitos, mientras que 2 es el número
de dígitos después de la coma. Tenga en cuenta que da lugar a los números de un teléfono antes de la coma es un
máximo de 4.

Relational fields
Los campos relacionales enlazan registros, ya sea del mismo modelo (jerarquías) o entre diferentes modelos.
Los tipos de campos relacionales son:
Many2one(other_model, ondelete='set null')
Un simple enlace a otro objeto:
print foo.other_id.name

Ver también
foreign keys

One2many(other_model, related_field)
Una relación virtual, inversa de una Many2one. Un One2many se comporta como un contenedor de
registros, el acceso resulta en un conjunto (posiblemente vacío) de registros:
for other in foo.other_ids:
print other.name

Peligro
Debido a que una One2many es una relación virtual, no debe haber un campo Many2one en el other_model, y su
nombre debe ser related_field

Many2many(other_model)
Bidireccional relación múltiple, cualquier registro en un lado puede estar relacionado con cualquier número de
registros en el otro lado. Se comporta como un recipiente de registros, el acceso también resulta en un
conjunto posiblemente vacío de registros:
for other in foo.other_ids:
print other.name

Ejercicio
las relaciones Many2one
El uso de un many2one, modifique las del curso y la Sesión modelos para reflejar su relación con otros modelos:

 Un curso tiene una responsabilidad del usuario; el valor de ese campo es un registro del modelo
integrado res.users.
 Una sesión tiene un instructor; el valor de ese campo es un registro del modelo integrado res.partner.
 Una sesión se relaciona con un supuesto; el valor de ese campo es un registro del
modelo openacademy.course y no es requerido.
 Adaptar los puntos de vista.

1. Añadir los correspondientes campos Many2one de los modelos, y


2. Añadirlos en las vistas.

openacademy/models.py

name = fields.Char(string="Title", required=True)


description = fields.Text()

responsible_id = fields.Many2one('res.users',
ondelete='set null', string="Responsible", index=True)

class Session(models.Model):
_name = 'openacademy.session'

start_date = fields.Date()
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")

instructor_id = fields.Many2one('res.partner', string="Instructor")


course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)

openacademy/views/openacademy.xml
<sheet>
<group>
<field name="name"/>
<field name="responsible_id"/>
</group>
<notebook>
<page string="Description">

</field>
</record>

<!-- override the automatically generated list view for courses -->
<record model="ir.ui.view" id="course_tree_view">
<field name="name">course.tree</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<tree string="Course Tree">
<field name="name"/>
<field name="responsible_id"/>
</tree>
</field>
</record>

<!-- window action -->


<!--
The following tag is an action definition for a "window action",

<form string="Session Form">


<sheet>
<group>
<group string="General">
<field name="course_id"/>
<field name="name"/>
<field name="instructor_id"/>
</group>
<group string="Schedule">
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
</group>
</group>
</sheet>
</form>
</field>
</record>

<!-- session tree/list view -->


<record model="ir.ui.view" id="session_tree_view">
<field name="name">session.tree</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<tree string="Session Tree">
<field name="name"/>
<field name="course_id"/>
</tree>
</field>
</record>

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


<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>

Ejercicio
Las relaciones inversas One2Many
Utilizando el inverso one2many campo relacional, modificar los modelos para reflejar la relación entre los cursos y
sesiones.

1. Modificar la clase Course y


2. Agregar el campo en la vista de formulario de curso.

openacademy/models.py
responsible_id = fields.Many2one('res.users',
ondelete='set null', string="Responsible", index=True)
session_ids = fields.One2many(
'openacademy.session', 'course_id', string="Sessions")

class Session(models.Model):

openacademy/views/openacademy.xml
<page string="Description">
<field name="description"/>
</page>
<page string="Sessions">
<field name="session_ids">
<tree string="Registered sessions">
<field name="name"/>
<field name="instructor_id"/>
</tree>
</field>
</page>
</notebook>
</sheet>

Ejercicio
Las relaciones múltiples Many2Many
Utilizando el Many2Many campo relacional, modificar la Sesión modelo para relacionar cada sesión a un conjunto
de asistentes. Los asistentes estarán representados por los registros asociados, por lo que se relacionan con el
modelo integrado res.partner. Adaptar las vistas en consecuencia.

1. Modificar la clase Session y


2. agregar el campo en la vista formulario.

openacademy/models.py
instructor_id = fields.Many2one('res.partner', string="Instructor")
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
attendee_ids = fields.Many2many('res.partner', string="Attendees")

openacademy/views/openacademy.xml
<field name="seats"/>
</group>
</group>
<label for="attendee_ids"/>
<field name="attendee_ids"/>
</sheet>
</form>
</field>

Herencia

Modelo de herencia
Odoo proporciona dos mecanismos de herencia para extender un modelo existente de una forma modular.
El primer mecanismo de herencia permite a un módulo para modificar el comportamiento de un modelo definido en
otro módulo:

 añadir campos a un modelo,


 anular la definición de campos en un modelo,
 añadir restricciones a un modelo,
 añadir métodos a un modelo,
 reemplazar los métodos existentes en un modelo.

El segundo mecanismo de herencia (delegación) permite vincular cada registro de un modelo para un registro en un
modelo padre, y proporciona un acceso transparente a los campos del registro padre.
Ver también

 _inherit
 _inherits

Vista herencia
En lugar de modificar las vistas existentes en su lugar (sobrescribiéndolas), Odoo proporciona una herencia de vistas
en la que las vistas de "extensión" de los hijos se aplican encima de las vistas de la raíz y pueden agregar o eliminar
contenido de sus padres.
Una vista de extensión hace referencia a su matriz usando el campo inherit_id, y en lugar de una sola vista su
campo arch se compone de cualquier número de elementos xpath de selección y alterar el contenido de su vista
padre:
<!—lista de categorias de ideas mejoradas -->
<record id="idea_category_list2" model="ir.ui.view">
<field name="name">id.category.list2</field>
<field name="model">idea.category</field>
<field name="inherit_id" ref="id_category_list"/>
<field name="arch" type="xml">
<!-- Encontrar la descripción del campo y agregar el campo
Idea_ids después de ella -->
<xpath expr="//field[@name='description']" position="after">
<field name="idea_ids" string="Number of ideas"/>
</xpath>
</field>
</record>

expr
Un XPath expresión seleccionar un solo elemento en la vista padre. Genera un error si coincide con ningún
elemento o más de uno
position
Operación que se aplicará al elemento coincidente:
inside
Añade el cuerpo de xpath en el extremo del elemento emparejado
replace
Sustituye el elemento emparejaron con el cuerpo de xpath, en sustitución de cualquier $0ocurrencia nodo en
el nuevo cuerpo con el elemento original,
before
Inserta el cuerpo xpath como un hermano antes del elemento emparejado
after
Inserta el cuerpo xpath como un hermano después del elemento emparejado
attributes
Altera los atributos del elemento emparejado usando elementos especiales attribute en el cuerpo de xpath
Tip
Cuando búsqueda de un solo elemento, el atributo position se puede establecer directamente sobre el elemento
que se encuentran. Ambas herencias a continuación le dará el mismo resultado.
<xpath expr="//field[@name='description']" position="after">
<field name="idea_ids" />
</xpath>

<field name="description" position="after">


<field name="idea_ids" />
</field>

Ejercicio
Alterar el contenido existente

 Utilizando el modelo de herencia, modificar el vigente socio modelo para añadir un campo booleano instructor , y un
campo Many2Many que corresponde a la relación de sesión - socio
 Usar la vista de la herencia, Muestra este campos en la vista formulario de empresa

Nota
Esta es la oportunidad para introducir el modo de desarrollador para inspeccionar la vista, encontrará su ID externo y
el lugar para poner el nuevo campo.

1. Crear un archivo openacademy/models/partner.py e importarlo en __init__.py


2. Crear un archivo openacademy/views/partner.xml y añadirlo a __manifest__.py

openacademy/__init__.py
# -*- coding: utf-8 -*-
from . import controllers
from . import models
from . import partner

openacademy/__manifest__.py
# 'security/ir.model.access.csv',
'templates.xml',
'views/openacademy.xml',
'views/partner.xml',
],
# only loaded in demonstration mode
'demo': [

openacademy/partner.py
# -*- coding: utf-8 -*-
from odoo import fields, models
class Partner(models.Model):
_inherit = 'res.partner'
# Agreggue una columna nueva al modelo res.partner, por defecto los partners no son
# instructores
instructor = fields.Boolean("Instructor", default=False)

session_ids = fields.Many2many('openacademy.session',
string="Attended Sessions", readonly=True)

openacademy/views/partner.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!-- Add instructor field to existing view -->
<record model="ir.ui.view" id="partner_instructor_form_view">
<field name="name">partner.instructor</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<notebook position="inside">
<page string="Sessions">
<group>
<field name="instructor"/>
<field name="session_ids"/>
</group>
</page>
</notebook>
</field>
</record>

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


<field name="name">Contacts</field>
<field name="res_model">res.partner</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="configuration_menu" name="Configuration"
parent="main_openacademy_menu"/>
<menuitem id="contact_menu" name="Contacts"
parent="configuration_menu"
action="contact_list_action"/>
</data>
</odoo>

Domains
En Odoo, dominios son valores que codifican las condiciones en registros. Un dominio es una lista de
los criterios utilizados para seleccionar un subconjunto de los registros de un modelo. Cada criterio es
un triplete, con un nombre de campo, un operador y un valor.
Por ejemplo, cuando se utiliza en el producto modelo de dominio de la siguiente selecciona todos los
servicios con un precio unitario mayor a 1000:
[('product_type', '=', 'service'), ('unit_price', '>', 1000)]

Por criterios predeterminados se combinan con un AND implícito. Los operadores lógicos &(Y), |(OR)
y !(NO) se pueden utilizar para combinar explícitamente criterios. Se utilizan en posición prefijo (el
operador se inserta antes de que sus argumentos en lugar de entre). Por ejemplo para seleccionar los
productos "que son servicios de O tienen un precio unitario que es NO entre 1000 y 2000":
['|',
('product_type', '=', 'service'),
'!', '&',
('unit_price', '>=', 1000),
('unit_price', '<', 2000)]

Un parametro domain puede ser añadido a los campos relacionales para limitar los registros válidos
para la relación cuando se trata de seleccionar registros en la interfaz del cliente.
Ejercicio
Dominios en los campos relacionales
Al seleccionar el instructor para una sesión , sólo los instructores (socios con instructorconjunto
a True) deben ser visibles.
openacademy/models.py
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")

instructor_id = fields.Many2one('res.partner', string="Instructor",


domain=[('instructor', '=', True)])
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
attendee_ids = fields.Many2many('res.partner', string="Attendees")

Nota
Un dominio declarado como una lista literal se evalúa en el lado del servidor y no puede referirse a valores dinámicos en el lado
derecho, un dominio declarado como una cadena es evaluada en el lado del cliente y permite los nombres de campo en el lado
derecho

Ejercicio
Dominios más complejos
Crear nuevas categorías de socios Maestro / Nivel 1 y Maestros / Nivel 2. El instructor para una sesión puede ser un
instructor o un maestro (de cualquier nivel).

1. Modificar la sesión del dominio del modelo


2. Modificar openacademy/view/partner.xmlpara acceder a categorías de socios :

openacademy/models.py
seats = fields.Integer(string="Number of seats")

instructor_id = fields.Many2one('res.partner', string="Instructor",


domain=['|', ('instructor', '=', True),
('category_id.name', 'ilike', "Teacher")])
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
attendee_ids = fields.Many2many('res.partner', string="Attendees")

openacademy/views/partner.xml
<menuitem id="contact_menu" name="Contacts"
parent="configuration_menu"
action="contact_list_action"/>

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


<field name="name">Contact Tags</field>
<field name="res_model">res.partner.category</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="contact_cat_menu" name="Contact Tags"
parent="configuration_menu"
action="contact_cat_list_action"/>

<record model="res.partner.category" id="teacher1">


<field name="name">Teacher / Level 1</field>
</record>
<record model="res.partner.category" id="teacher2">
<field name="name">Teacher / Level 2</field>
</record>
</data>
</odoo>

Los campos calculados y los valores predeterminados


Los campos hasta ahora se han almacenado directamente en y recuperados directamente de la base de datos. Los
campos también puedes ser calculadas. En ese caso, el valor del campo no se recupera de la base de datos pero
calcula sobre la marcha llamando a un método del modelo.
Para crear un campo calculado, cree un campo y establezca su atributo compute con el nombre de un método. El
método de cálculo debería simplemente establecer el valor del campo para calcular en cada registro self.

Peligro
self es una colección
El objeto self es un conjunto de registros, es decir, una colección ordenada de registros. Es compatible con las
operaciones estándar de Python en colecciones, como len(self)y iter(self), además de las operaciones de
configuración adicionales como recs1 + recs2.
Interactuando sobre registros self uno por uno, donde cada registro es en sí mismo es una colección de tamaño 1.
Se puede acceder a / asignar campos en los registros individuales mediante el uso de la notación de puntos, al igual
que record.name.
import random
from odoo import models, fields, api

class ComputedModel(models.Model):
_name = 'test.computed'

name = fields.Char(compute='_compute_name')

@api.multi
def _compute_name(self):
for record in self:
record.name = str(random.randint(1, 1e6))

Dependencias
El valor de un campo computado por lo general depende de los valores de otros campos en el registro computado. El
ORM espera que el desarrollador especifique esas dependencias en el método de cálculo con el
decorador depends(). Las dependencias dadas son utilizadas por el ORM para disparar el recálculo del campo cada
vez que alguna de sus dependencias se han modificado:

from odoo import models, fields, api

class ComputedModel(models.Model):
_name = 'test.computed'

name = fields.Char(compute='_compute_name')
value = fields.Integer()

@api.depends('value')
def _compute_name(self):
for record in self:
record.name = "Record with value %s" % record.value

Ejercicio
Los campos calculados

 Añadir el porcentaje de asientos ocupados a la Sesión modelo


 Mostrar que el campo en el árbol y formar puntos de vista
 Mostrar el campo como una barra de progreso

1. Añadir un campo calculado a Sesión


2. Mostrar el campo en la sesión de vista:

openacademy/models.py

course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
attendee_ids = fields.Many2many('res.partner', string="Attendees")

taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')

@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
for r in self:
if not r.seats:
r.taken_seats = 0.0
else:
r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats
openacademy/views/openacademy.xml
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="taken_seats" widget="progressbar"/>
</group>
</group>
<label for="attendee_ids"/>

<tree string="Session Tree">


<field name="name"/>
<field name="course_id"/>
<field name="taken_seats" widget="progressbar"/>
</tree>
</field>
</record>

Valores predeterminados
Cualquier campo puede dar un valor por defecto. En la definición de campo, añadir la opción default=X donde X es o
bien un valor Python literal (boolean, integer, float, string), o una función de tomar un conjunto de registros y devolver
un valor:
name = fields.Char(default="Unknown")
user_id = fields.Many2one('res.users', default=lambda self: self.env.user)

Nota
El objeto self.env da acceso a los parámetros de solicitud y otras cosas útiles:

 self.env.cro self._cr es la base de datos cursor objeto; se utiliza para consultar la base de datos
 self.env.uido self._uid es el ID de la base de datos del usuario actual
 self.env.user es el registro del usuario actual
 self.env.contexto self._context es el diccionario de contexto
 self.env.ref(xml_id) devuelve el registro correspondiente a un id XML
 self.env[model_name] devuelve una instancia de la modelo dado

Ejercicio
objetos activos - Los valores por defecto
 Definir el valor por defecto fecha_inicial como en la actualidad (ver Date).
 Añadir un campo active en la sesión de clase, y establecer sesiones como activa por defecto.
openacademy/models.py
_name = 'openacademy.session'

name = fields.Char(required=True)
start_date = fields.Date(default=fields.Date.today)
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
active = fields.Boolean(default=True)

instructor_id = fields.Many2one('res.partner', string="Instructor",


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

openacademy/views/openacademy.xml
<field name="course_id"/>
<field name="name"/>
<field name="instructor_id"/>
<field name="active"/>
</group>
<group string="Schedule">
<field name="start_date"/>
Nota
Odoo tiene reglas incorporadas en la toma de los campos con an activefield establecido
en Falseinvisible.

onchange
El mecanismo de "onchange" (en cambio) proporciona una forma para que la interfaz cliente actualice un formulario
siempre que el usuario haya rellenado un valor en un campo, sin guardar nada en la base de datos.
Por ejemplo, supongamos que un modelo tiene tres campos amount, unit_pricey price, y desea actualizar el precio
en el formulario cuando se modifica cualquiera de los otros campos. Para lograr esto, definir un método en el que self
representa el registro en la vista de la forma, y se adorna con onchange() especificar en qué campo tiene que ser
activado. Cualquier cambio que realice en self se verá reflejado en el formulario.
<!-- content of form view -->
<field name="amount"/>
<field name="unit_price"/>
<field name="price" readonly="1"/>

# onchange handler
@api.onchange('amount', 'unit_price')
def _onchange_price(self):
# set auto-changing field
self.price = self.amount * self.unit_price
# Can optionally return a warning and domains
return {
'warning': {
'title': "Something bad happened",
'message': "It was very bad indeed",
}
}

Para los campos calculados, el comportamiento valorado onchange es una función de como se puede ver jugando
con el formulario Sesión: cambiar el número de asientos o participantes, y la barra de progreso de taken_seats se
actualiza automáticamente.

Ejercicio
Advertencia
Añadir un onchange explícita para advertir sobre los valores no válidos, como un número negativo de asientos, o
más participantes que los asientos.
openacademy/models.py
r.taken_seats = 0.0
else:
r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats

@api.onchange('seats', 'attendee_ids')
def _verify_valid_seats(self):
if self.seats < 0:
return {
'warning': {
'title': "Incorrect 'seats' value",
'message': "The number of available seats may not be negative",
},
}
if self.seats < len(self.attendee_ids):
return {
'warning': {
'title': "Too many attendees",
'message': "Increase seats or remove excess attendees",
},
}

Restricciones de un modelo
Odoo ofrece dos formas de configurar invariantes verificadas de forma automática: Python constraintsy SQL
constraints.
Una restricción Python se define como un método decorado con constrains(), y se invoca en un conjunto de
registros. El decorador especifica qué campos están involucrados en la restricción, por lo que la restricción se evalúa
automáticamente cuando uno de ellos se modifica. Se espera que el método para producir una excepción si es
invariante no está satisfecho:
from odoo.exceptions import ValidationError

@api.constrains('age')
def _check_something(self):
for record in self:
if record.age > 20:
raise ValidationError("Your record is too old: %s" % record.age)
# all records passed the test, don't return anything

Ejercicio
Añadir restricciones de Python
Añadir una restricción que comprueba que el instructor no está presente en los asistentes de su / su propia sesión.
openacademy/models.py
# -*- coding: utf-8 -*-

from odoo import models, fields, api, exceptions

class Course(models.Model):
_name = 'openacademy.course'

'message': "Increase seats or remove excess attendees",


},
}

@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
for r in self:
if r.instructor_id and r.instructor_id in r.attendee_ids:
raise exceptions.ValidationError("A session's instructor can't be an attendee")

Las limitaciones de SQL se definen a través del atributo model _sql_constraints. Este último es asignado a una lista
de triples de cuerdas (name, sql_definition, message), donde name es un nombre de restricción de SQL
válida, sql_definition es un table_constraint expresión, y message es el mensaje de error.

Ejercicio
Añadir restricciones de SQL
Con la ayuda de la documentación de PostgreSQL , añadir las siguientes limitaciones:

1. Compruebe que la descripción del curso y el título del curso son diferentes
2. Hacer el nombre del curso único

openacademy/models.py
session_ids = fields.One2many(
'openacademy.session', 'course_id', string="Sessions")

_sql_constraints = [
('name_description_check',
'CHECK(name != description)',
"The title of the course should not be the description"),

('name_unique',
'UNIQUE(name)',
"The course title must be unique"),
]

class Session(models.Model):
_name = 'openacademy.session'
Ejercicio
Ejercicio 6 - Añadir una opción duplicado
Puesto que hemos añadido una restricción para el nombre del curso singularidad, no es posible utilizar la función de
"duplicar" más ( Formulario ‣ duplicados ).
Vuelva a aplicar su propio método de "copiar" lo que permite duplicar el objeto del curso, cambiar el nombre original
en "Copia de [nombre original]".
openacademy/models.py
session_ids = fields.One2many(
'openacademy.session', 'course_id', string="Sessions")

@api.multi
def copy(self, default=None):
default = dict(default or {})

copied_count = self.search_count(
[('name', '=like', u"Copy of {}%".format(self.name))])
if not copied_count:
new_name = u"Copy of {}".format(self.name)
else:
new_name = u"Copy of {} ({})".format(self.name, copied_count)

default['name'] = new_name
return super(Course, self).copy(default)

_sql_constraints = [
('name_description_check',
'CHECK(name != description)',

Vistas avanzada

Vistas de árbol
Las vistas de árbol pueden tener atributos complementarios para personalizar aún más su comportamiento:

decoration-{$name}
Permite cambiar el estilo del texto de una fila basado en los atributos del registro correspondiente.

Los valores son expresiones de Python. Para cada registro, la expresión se evalúa con los atributos del registro como
valores de contexto y si es true, el estilo correspondiente se aplica a la fila. Otros valores de contexto son uid(el id del
usuario actual) y current_date(la fecha actual como una cadena de la forma yyyy-MM-dd).

{$name}puede ser bf( font-weight: bold), it ( font-style: italic), o cualquier color de contextual
bootstrap ( danger, info, muted, primary, successo warning).
<tree string="Idea Categories" decoration-info="state=='draft'"
decoration-danger="state=='trashed'">
<field name="name"/>
<field name="state"/>
</tree>

editable
Ya sea "top"o "bottom". Hace que la vista editable árbol en el lugar (en lugar de tener que ir a través de la
vista de formulario), el valor es la posición en la que aparecen nuevas filas.

Ejercicio
Colorear lista
Modificar la vista de árbol Sesión de tal manera que las sesiones que duran menos de 5 días son de color azul, y los
que duran más de 15 días son de color rojo.
Modificar la vista de árbol de sesión:
openacademy/views/openacademy.xml
<field name="name">session.tree</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<tree string="Session Tree" decoration-info="duration&lt;5" decoration-danger="duration&
gt;15">
<field name="name"/>
<field name="course_id"/>
<field name="duration" invisible="1"/>
<field name="taken_seats" widget="progressbar"/>
</tree>
</field>

Calendarios
Muestra los registros como los eventos del calendario. Su elemento raíz es <calendar>y sus atributos más comunes
son:
color
El nombre del campo utilizado para la segmentación de color . Los colores se distribuyen automáticamente a
eventos, pero los eventos en el mismo segmento de color (registros que tienen el mismo valor para
su @colorcampo) se les da el mismo color.
date_start
Campo de registro de la celebración del inicio de fecha / hora para el evento
date_stop (Opcional)
Campo de registro sujeta el extremo de fecha / hora para el evento
field (para definir la etiqueta para cada evento del calendario)
<calendar string="Ideas" date_start="invent_date" color="inventor_id">
<field name="name"/>
</calendar>

Ejercicio
Vista del calendario
Agregar una vista de calendario para la Sesión modelo que permite al usuario ver los eventos asociados a la
Academia abierto.
1. Añadir un campo end_date calculado a partir de start_date y duration
Tip
La función inversa hace que el escribible campo, y permite mover las sesiones (a través de arrastrar y soltar)
en la vista de calendario

2. Agregar una vista de calendario a la Sesión modelo


3. Y añadir la vista del calendario a la sesión de las acciones del modelo

openacademy/models.py
# -*- coding: utf-8 -*-

from datetime import timedelta


from odoo import models, fields, api, exceptions

class Course(models.Model):

attendee_ids = fields.Many2many('res.partner', string="Attendees")

taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')


end_date = fields.Date(string="End Date", store=True,
compute='_get_end_date', inverse='_set_end_date')

@api.depends('seats', 'attendee_ids')
def _taken_seats(self):

},
}
@api.depends('start_date', 'duration')
def _get_end_date(self):
for r in self:
if not (r.start_date and r.duration):
r.end_date = r.start_date
continue

# añadir duración a start_date, pero: lunes + 5 días = sábado, por lo que


# Restar un segundo para obtener el viernes en su lugar
start = fields.Datetime.from_string(r.start_date)
duration = timedelta(days=r.duration, seconds=-1)
r.end_date = start + duration

def _set_end_date(self):
for r in self:
if not (r.start_date and r.end_date):
continue

# Calcule la diferencia entre las fechas, pero: Viernes - Lunes = 4 días,


# Así que agregue un día para obtener 5 días en su lugar
start_date = fields.Datetime.from_string(r.start_date)
end_date = fields.Datetime.from_string(r.end_date)
r.duration = (end_date - start_date).days + 1

@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
for r in self:

openacademy/views/openacademy.xml
</field>
</record>

<!-- calendar view -->


<record model="ir.ui.view" id="session_calendar_view">
<field name="name">session.calendar</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<calendar string="Session Calendar" date_start="start_date"
date_stop="end_date"
color="instructor_id">
<field name="name"/>
</calendar>
</field>
</record>

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


<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
</record>

<menuitem id="session_menu" name="Sessions"

Vistas de búsqueda
Los elementos de la vista de busqueda <field> pueden tener una @filter_domain que anula el dominio
generado para buscar en el campo dado. En el dominio dado, self representa el valor introducido por
el usuario. En el siguiente ejemplo, se utiliza para buscar en ambos campos namey description.
Las Vistas de búsqueda también pueden contener elementos <filter> que actúan como palancas para
búsquedas predefinidas. Los filtros deben tener uno de los siguientes atributos:
domain

Añadir el dominio dado a la búsqueda actual


context
Añadir un poco de contexto a la búsqueda actual; utilizar la clave group_by de los resultados del
grupo en el nombre de campo dado
<search string="Ideas">
<field name="name"/>
<field name="description" string="Name and description"
filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/>
<field name="inventor_id"/>
<field name="country_id" widget="selection"/>

<filter name="my_ideas" string="My Ideas"


domain="[('inventor_id', '=', uid)]"/>
<group string="Group By">
<filter name="group_by_inventor" string="Inventor"
context="{'group_by': 'inventor_id'}"/>
</group>
</search>

Para utilizar una vista de búsqueda no predeterminado en una acción, debe estar vinculada con el campo
search_view_id del registro de la acción.
La acción también puede establecer valores predeterminados para los campos de búsqueda a través de sus campos
context: claves de contexto de forma inicializará nombre_campo con el valor proporcionado. Los filtros de búsqueda
deben tener una opción para tener una forma predeterminada y se comportan como booleanos (sólo pueden ser
activadas por defecto).search_default_field_name@name

Ejercicio
Vistas de búsqueda

1. Añadir un botón para filtrar los cursos para los cuales el usuario actual es el responsable en la vista de
búsqueda de cursos. Que sea seleccionada por defecto.
2. Añadir un botón para cursos en grupo por el usuario responsable.

openacademy/views/openacademy.xml
<search>
<field name="name"/>
<field name="description"/>
<filter name="my_courses" string="My Courses"
domain="[('responsible_id', '=', uid)]"/>
<group string="Group By">
<filter name="by_responsible" string="Responsible"
context="{'group_by': 'responsible_id'}"/>
</group>
</search>
</field>
</record>

<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context" eval="{'search_default_my_courses': 1}"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">Create the first course
</p>

Gantt
Gráficos de barras horizontales normalmente se utilizan para mostrar la planificación y el avance del proyecto, su
elemento es la raíz <gantt>.
<gantt string="Ideas"
date_start="invent_date"
date_stop="date_finished"
progress="progress"
default_group_by="inventor_id" />
Ejercicio
Diagramas de Gantt
Añadir un diagrama de Gantt que permite al usuario ver la programación de sesiones relacionado con el módulo
abierto Academia. Las sesiones deben ser agrupadas por el instructor.

1. Crear un campo calculado expresando la duración de la sesión en horas


2. Añadir la definición de la vista de Gantt, y añadir la vista de Gantt para la sesión de la acción del modelo

openacademy/models.py
end_date = fields.Date(string="End Date", store=True,
compute='_get_end_date', inverse='_set_end_date')

hours = fields.Float(string="Duration in hours",


compute='_get_hours', inverse='_set_hours')

@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
for r in self:

end_date = fields.Datetime.from_string(r.end_date)
r.duration = (end_date - start_date).days + 1

@api.depends('duration')
def _get_hours(self):
for r in self:
r.hours = r.duration * 24

def _set_hours(self):
for r in self:
r.duration = r.hours / 24

@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
for r in self:

openacademy/views/openacademy.xml
</field>
</record>

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


<field name="name">session.gantt</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<gantt string="Session Gantt" color="course_id"
date_start="start_date" date_delay="hours"
default_group_by='instructor_id'>
<field name="name"/>
</gantt>
</field>
</record>

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


<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,gantt</field>
</record>

<menuitem id="session_menu" name="Sessions"

Vistas de gráficos
Vistas de gráficos permiten descripción y análisis de los modelos agregados, su elemento es la raíz <graph>.

Nota
Vistas de giro (elemento <pivot>) una tabla multidimensional, permite la selección de los contribuyentes y
dimensiones para obtener el conjunto de datos agregada a la derecha antes de pasar a una visión más gráfica. La
vista de pivote comparte la misma definición contenido como vistas de gráficos.
Las Vistas de gráficos tienen 4 modos de visualización, el modo por defecto se selecciona usando el @type atributo.
Bar (por defecto)
un gráfico de barras, la primera dimensión se utiliza para definir los grupos en el eje horizontal, otras
dimensiones definen bares agregados dentro de cada grupo.
Por bares predeterminados son de lado a lado, que pueden ser apilados mediante el
uso @stacked="True"de la<graph>
Line
Gráfico de líneas de 2 dimensiones
Pie
Pastel de 2 dimensiones
Vistas de gráficos contienen <field>con una obligatoria @typeatributo tomar los valores:
row (default)
El campo debe ser agregado por defecto
measure

El campo debe ser agregado en lugar de estar agrupado


Advertencia
Vistas de gráficos realizan agregaciones en los valores de base de datos, que no funcionan con campos calculados
no almacenados.

Ejercicio
Vista gráfico
Añadir una vista de gráfico en el objeto Session que muestra, para cada supuesto, el número de
asistentes bajo la forma de un gráfico de barras.

1. Añadir el número de asistentes como un campo calculado almacenado


2. A continuación, añadir la vista relevante

openacademy/models.py
hours = fields.Float(string="Duration in hours",
compute='_get_hours', inverse='_set_hours')

attendees_count = fields.Integer(
string="Attendees count", compute='_get_attendees_count', store=True)

@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
for r in self:

for r in self:
r.duration = r.hours / 24

@api.depends('attendee_ids')
def _get_attendees_count(self):
for r in self:
r.attendees_count = len(r.attendee_ids)

@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
for r in self:

openacademy/views/openacademy.xml
</field>
</record>

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


<field name="name">openacademy.session.graph</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<graph string="Participations by Courses">
<field name="course_id"/>
<field name="attendees_count" type="measure"/>
</graph>
</field>
</record>

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


<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,gantt,graph</field>
</record>

<menuitem id="session_menu" name="Sessions"

Kanban
Se utiliza para organizar las tareas, procesos de producción, etc. Su elemento raíz es <kanban>.
Una vista kanban muestra un conjunto de tarjetas, posiblemente agrupadas en columnas. Cada tarjeta representa un
registro, y cada columna los valores de un campo de agregación.
Por ejemplo, las tareas del proyecto pueden ser organizados por etapas (cada columna es una etapa), o por el
responsable (cada columna es un usuario), y así sucesivamente.
Las Vistas Kanban definen la estructura de cada tarjeta como una mezcla de elementos de forma (incluyendo HTML
básico) y Qweb .

Ejercicio
Kanban vista
Añadir una vista que muestra Kanban sesiones agrupadas por supuesto (columnas son, pues, los
cursos).

1. Añadir un entero colorcampo de la Sesión modelo


2. Añadir la vista Kanban y actualizar la acción

openacademy/models.py
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
active = fields.Boolean(default=True)
color = fields.Integer()

instructor_id = fields.Many2one('res.partner', string="Instructor",


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

openacademy/views/openacademy.xml
</record>

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


<field name="name">openacad.session.kanban</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<kanban default_group_by="course_id">
<field name="color"/>
<templates>
<t t-name="kanban-box">
<div
t-attf-class="oe_kanban_color_{{kanban_getcolor(record.color.raw_val
ue)}}
oe_kanban_global_click_edit oe_semantic_html_override
oe_kanban_card {{record.group_fancy==1 ? 'oe_kanban_ca
rd_fancy' : ''}}">
<div class="oe_dropdown_kanban">
<!-- dropdown menu -->
<div class="oe_dropdown_toggle">
<i class="fa fa-bars fa-lg"/>
<ul class="oe_dropdown_menu">
<li>
<a type="delete">Delete</a>
</li>
<li>
<ul class="oe_kanban_colorpicker"
data-field="color"/>
</li>
</ul>
</div>
<div class="oe_clear"></div>
</div>
<div t-attf-class="oe_kanban_content">
<!-- title -->
Session name:
<field name="name"/>
<br/>
Start date:
<field name="start_date"/>
<br/>
duration:
<field name="duration"/>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>

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


<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,gantt,graph,kanban</field>
</record>

<menuitem id="session_menu" name="Sessions"


parent="openacademy_menu"

Workflows
Los flujos de trabajo son modelos asociados a objetos de negocio que describen su dinámica. Los flujos de trabajo
también se utilizan para realizar un seguimiento de los procesos que evolucionan con el tiempo.

Ejercicio
Casi un flujo de trabajo
Añadir un campo state a la Sesión modelo. Se utiliza para definir un flujo de trabajo-ish.
Una sesion puede tener tres estados posibles: Proyecto (por defecto), confirmados y hecho.
En la forma de sesiones, agregar un campo (sólo lectura) para visualizar el estado, y los botones para
cambiarlo. Las transiciones válidas son:

 Proyecto -> Confirmado


 Confirmado -> Proyecto
 Confirmado -> Hecho
 Hecho -> Proyecto

1. Añadir un nuevo statecampo


2. Añadir los métodos de la transición del estado, los que se puede llamar desde botones de vista para cambiar
el estado del registro
3. Y añadir los botones correspondientes a la vista de formulario de la sesión

openacademy/models.py
attendees_count = fields.Integer(
string="Attendees count", compute='_get_attendees_count', store=True)
state = fields.Selection([
('draft', "Draft"),
('confirmed', "Confirmed"),
('done', "Done"),
], default='draft')

@api.multi
def action_draft(self):
self.state = 'draft'

@api.multi
def action_confirm(self):
self.state = 'confirmed'

@api.multi
def action_done(self):
self.state = 'done'

@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
for r in self:

openacademy/views/openacademy.xml
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<form string="Session Form">
<header>
<button name="action_draft" type="object"
string="Reset to draft"
states="confirmed,done"/>
<button name="action_confirm" type="object"
string="Confirm" states="draft"
class="oe_highlight"/>
<button name="action_done" type="object"
string="Mark as done" states="confirmed"
class="oe_highlight"/>
<field name="state" widget="statusbar"/>
</header>

<sheet>
<group>
<group string="General">

Los flujos de trabajo pueden estar asociadas con cualquier objeto en Odoo, y son totalmente adaptable. Los flujos de
trabajo se utilizan para estructurar y gestionar los ciclos de vida de los objetos de negocio y documentos, y definir las
transiciones, triggers, etc., con herramientas gráficas. Flujos de trabajo, actividades nodos (o acciones) y las
transiciones (condiciones) se declaran como registros XML, como de costumbre. Las fichas que navegan en los flujos
de trabajo se denominan elementos de trabajo.
Advertencia
Un flujo de trabajo asociado a un modelo sólo se crea cuando se crean los registros del modelo. Por lo tanto no hay
una instancia de flujo de trabajo asociado con casos de sesión creadas antes de la definición de flujo de trabajo

Ejercicio
Work Flow
Vuelva a colocar la ad-hoc el flujo de trabajo Sesión utilizando un flujo de trabajo real. Transformar la vista Sesión
por lo que sus botones llamar el flujo de trabajo en lugar de los métodos del modelo.
openacademy/__manifest__.py
'templates.xml',
'views/openacademy.xml',
'views/partner.xml',
'views/session_workflow.xml',
],
# only loaded in demonstration mode
'demo': [

openacademy/models.py
('draft', "Draft"),
('confirmed', "Confirmed"),
('done', "Done"),
])

@api.multi
def action_draft(self):

openacademy/views/openacademy.xml
<field name="arch" type="xml">
<form string="Session Form">
<header>
<button name="draft" type="workflow"
string="Reset to draft"
states="confirmed,done"/>
<button name="confirm" type="workflow"
string="Confirm" states="draft"
class="oe_highlight"/>
<button name="done" type="workflow"
string="Mark as done" states="confirmed"
class="oe_highlight"/>
<field name="state" widget="statusbar"/>

openacademy/views/session_workflow.xml
<odoo>
<data>
<record model="workflow" id="wkf_session">
<field name="name">OpenAcademy sessions workflow</field>
<field name="osv">openacademy.session</field>
<field name="on_create">True</field>
</record>

<record model="workflow.activity" id="draft">


<field name="name">Draft</field>
<field name="wkf_id" ref="wkf_session"/>
<field name="flow_start" eval="True"/>
<field name="kind">function</field>
<field name="action">action_draft()</field>
</record>
<record model="workflow.activity" id="confirmed">
<field name="name">Confirmed</field>
<field name="wkf_id" ref="wkf_session"/>
<field name="kind">function</field>
<field name="action">action_confirm()</field>
</record>
<record model="workflow.activity" id="done">
<field name="name">Done</field>
<field name="wkf_id" ref="wkf_session"/>
<field name="kind">function</field>
<field name="action">action_done()</field>
</record>

<record model="workflow.transition" id="session_draft_to_confirmed">


<field name="act_from" ref="draft"/>
<field name="act_to" ref="confirmed"/>
<field name="signal">confirm</field>
</record>
<record model="workflow.transition" id="session_confirmed_to_draft">
<field name="act_from" ref="confirmed"/>
<field name="act_to" ref="draft"/>
<field name="signal">draft</field>
</record>
<record model="workflow.transition" id="session_done_to_draft">
<field name="act_from" ref="done"/>
<field name="act_to" ref="draft"/>
<field name="signal">draft</field>
</record>
<record model="workflow.transition" id="session_confirmed_to_done">
<field name="act_from" ref="confirmed"/>
<field name="act_to" ref="done"/>
<field name="signal">done</field>
</record>
</data>
</odoo>
Tip
Con el fin de comprobar si las instancias del flujo de trabajo se crean correctamente junto sesiones, vaya
a instancias Ajustes ‣ ‣ ‣ flujos de trabajo técnico

Ejercicio
Transiciones automáticas
Transforma automáticamente las sesiones de borrador a confirmado cuando se reservan más de la mitad de los
asientos de la sesión.
openacademy/views/session_workflow.xml
<field name="act_to" ref="done"/>
<field name="signal">done</field>
</record>

<record model="workflow.transition" id="session_auto_confirm_half_filled">


<field name="act_from" ref="draft"/>
<field name="act_to" ref="confirmed"/>
<field name="condition">taken_seats &gt; 50</field>
</record>
</data>
</odoo>

Ejercicio
Las acciones del servidor
Reemplazar los métodos de Python para la sincronización de estado de la sesión por las acciones del servidor.
Tanto el flujo de trabajo y las acciones del servidor podrían haber sido creados por completo de la interfaz de
usuario.
openacademy/views/session_workflow.xml
<field name="on_create">True</field>
</record>

<record model="ir.actions.server" id="set_session_to_draft">


<field name="name">Set session to Draft</field>
<field name="model_id" ref="model_openacademy_session"/>
<field name="code">
model.search([('id', 'in', context['active_ids'])]).action_draft()
</field>
</record>
<record model="workflow.activity" id="draft">
<field name="name">Draft</field>
<field name="wkf_id" ref="wkf_session"/>
<field name="flow_start" eval="True"/>
<field name="kind">dummy</field>
<field name="action"></field>
<field name="action_id" ref="set_session_to_draft"/>
</record>

<record model="ir.actions.server" id="set_session_to_confirmed">


<field name="name">Set session to Confirmed</field>
<field name="model_id" ref="model_openacademy_session"/>
<field name="code">
model.search([('id', 'in', context['active_ids'])]).action_confirm()
</field>
</record>
<record model="workflow.activity" id="confirmed">
<field name="name">Confirmed</field>
<field name="wkf_id" ref="wkf_session"/>
<field name="kind">dummy</field>
<field name="action"></field>
<field name="action_id" ref="set_session_to_confirmed"/>
</record>

<record model="ir.actions.server" id="set_session_to_done">


<field name="name">Set session to Done</field>
<field name="model_id" ref="model_openacademy_session"/>
<field name="code">
model.search([('id', 'in', context['active_ids'])]).action_done()
</field>
</record>
<record model="workflow.activity" id="done">
<field name="name">Done</field>
<field name="wkf_id" ref="wkf_session"/>
<field name="kind">dummy</field>
<field name="action"></field>
<field name="action_id" ref="set_session_to_done"/>
</record>

<record model="workflow.transition" id="session_draft_to_confirmed">

Security
Los mecanismos de control de acceso deben estar configurados para lograr una política de seguridad coherente.

Mecanismos de control de acceso basadas en grupos


Los grupos se crean como registros normales en el modelo res.groups, y el acceso a través del menú otorgado
definiciones de menú. Sin embargo, incluso sin un menú, objetos todavía pueden ser accesibles indirectamente, lo
que los permisos de nivel de objeto real (leer, escribir, crear, desvincular) debe definirse para grupos. Por lo general,
se insertan a través de archivos CSV dentro de los módulos. También es posible restringir el acceso a los campos
específicos en una visión o un objeto utilizando grupos del campo de atributo.

Derechos de acceso
Los derechos de acceso se definen como registros del modelo ir.model.access. Cada derecho de acceso está
asociada a un modelo, un grupo (o hay un grupo para el acceso global), y un conjunto de permisos: leer, escribir,
crear, desvincular. Estos derechos de acceso se crean normalmente por un archivo CSV nombre de su modelo
ir.model.access.csv.
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_idea_idea,idea.idea,model_idea_idea,base.group_user,1,1,1,0
access_idea_vote,idea.vote,model_idea_vote,base.group_user,1,1,1,0

Ejercicio
Añadir el control de acceso a través de la interfaz de Odoo
Crear un nuevo usuario "John Smith". A continuación, crear un grupo "OpenAcademy / Sesión Lea" con acceso de
lectura a la Sesión modelo.

1. Crear un nuevo usuario John Smith a través de Configuración de usuarios ‣ ‣ Usuarios


2. Crear un nuevo grupo session_read a través de la configuración de usuarios ‣ ‣ Grupos , debe tener
acceso de lectura en la sesión de modelo
3. Editar John Smith para que sean un miembro de session_read
4. Inicie la sesión como John Smith para comprobar los derechos de acceso son correctos

Ejercicio
Añadir el control de acceso a través de los archivos de datos en el módulo
El uso de archivos de datos,

 Crear un grupo OpenAcademy / Gestor con pleno acceso a todos los modelos OpenAcademy
 Hacer Sesión y Curso leídos por todos los usuarios

1. Crear un nuevo archivo openacademy/security/security.xmlpara mantener el grupo Administrador de


OpenAcademy
2. Editar el archivo openacademy/security/ir.model.access.csvcon los derechos de acceso a los modelos
3. Finalmente actualizar openacademy/__manifest__.pypara añadir los nuevos archivos de datos a ella.

openacademy/__manifest__.py
# always loaded
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'templates.xml',
'views/openacademy.xml',
'views/partner.xml',

openacademy/security/ir.model.access.csv
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
course_manager,course manager,model_openacademy_course,group_manager,1,1,1,1
session_manager,session manager,model_openacademy_session,group_manager,1,1,1,1
course_read_all,course all,model_openacademy_course,,1,0,0,0
session_read_all,session all,model_openacademy_session,,1,0,0,0

openacademy/security/security.xml
<odoo>
<data>
<record id="group_manager" model="res.groups">
<field name="name">OpenAcademy / Manager</field>
</record>
</data>
</odoo>

Record rules
Una regla de registro restringe los derechos de acceso a un subconjunto de registros del modelo dado. Una regla es
un registro de modelo ir.rule, y se asocia a un modelo, un número de grupos (campo Many2Many), permisos al que
se aplica la restricción, y un dominio. El dominio específica a la que registra los derechos de acceso son limitadas.
He aquí un ejemplo de una regla que impide la eliminación de los cables que no están en el estado cancel. Observe
que el valor del campo groups debe seguir la misma convención como el método write()del ORM.
<record id="delete_cancelled_only" model="ir.rule">
<field name="name">Only cancelled leads may be deleted</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="1" />
<field name="domain_force">[('state','=','cancel')]</field>
</record>

Ejercicio
Regla de registro
Añadir una regla de registro para el modelo curso y el grupo "OpenAcademy /Manager", que restringe write
y unlink accede a la responsable de un curso. Si un curso no tiene ninguna responsabilidad, todos los usuarios del
grupo debe ser capaz de modificarlo.
Crear una nueva regla en openacademy/security/security.xml:
openacademy/security/security.xml
<record id="group_manager" model="res.groups">
<field name="name">OpenAcademy / Manager</field>
</record>

<record id="only_responsible_can_modify" model="ir.rule">


<field name="name">Only Responsible can modify Course</field>
<field name="model_id" ref="model_openacademy_course"/>
<field name="groups" eval="[(4, ref('openacademy.group_manager'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="1"/>
<field name="domain_force">
['|', ('responsible_id','=',False),
('responsible_id','=',user.id)]
</field>
</record>
</data>
</odoo>

Wizards
Los Wizards describen sesiones interactivas con el usuario (o cuadros de diálogo) a través de formularios
dinámicos. Un asistente es simplemente un modelo que amplía la clase TransientModel en lugar de Model. La
clase TransientModel se extiende Model y reutilización de todos sus mecanismos existentes, con las siguientes
particularidades:

 Registros del asistente no están destinados a ser persistentes; que se eliminan automáticamente de la base
de datos después de un cierto tiempo. Es por eso que se llaman transitoria.
 Asistente modelos no requieren derechos de acceso explícitos: los usuarios tienen todos los permisos en los
registros del asistente.
 Registros asistente puede referirse a los registros regulares o registros del asistente a través de campos
many2one, pero los registros regulares no pueden referirse a los registros del asistente través de un campo
many2one.

Queremos crear un asistente que permiten a los usuarios crear asistentes para una sesión particular, o para obtener
una lista de las sesiones a la vez.

Ejercicio
Definir el asistente
Crear un modelo de mago con una relación many2one con la Sesión de modelo y una relación Many2Many con
el socio de modelo.
Añadir un nuevo archivo openacademy/wizard.py:
openacademy/__init__.py
from . import controllers
from . import models
from . import partner
from . import wizard

openacademy/wizard.py
# -*- coding: utf-8 -*-

from odoo import models, fields, api

class Wizard(models.TransientModel):
_name = 'openacademy.wizard'

session_id = fields.Many2one('openacademy.session',
string="Session", required=True)
attendee_ids = fields.Many2many('res.partner', string="Attendees")

Lanzamientos de wizards
Los wizards son lanzados por registros ir.actions.act_window, con el campo target se establece en el
valor new. Este último se abre la vista asistente en una ventana emergente. La acción puede ser provocada por un
elemento de menú.
Hay otra manera de iniciar el asistente de: utilizar un ir.actions.act_window disco como el anterior, pero con un
campo adicional src_model que especifica en el contexto del modelo de la acción, que está disponible. El asistente
aparecerá en las acciones contextuales del modelo, por encima de la vista principal. Debido a algunos ganchos
internos en el ORM, tal acción se declara en XML con la etiqueta act_window.
<act_window id="launch_the_wizard"
name="Launch the Wizard"
src_model="context.model.name"
res_model="wizard.model.name"
view_mode="form"
target="new"
key2="client_action_multi"/>

Para Wizards vistas regulares y sus botones pueden utilizar el atributo special="cancel" para cerrar la ventana del
asistente sin guardar.

Ejercicio
Iniciar el asistente

1. Definir una vista de formulario para el asistente.


2. Añadir la acción para poner en marcha en el marco de la Sesión modelo.
3. Definir un valor predeterminado para el campo de sesión en el asistente; utilizar el parámetro de
contexto self._context para recuperar la sesión actual.

openacademy/wizard.py
class Wizard(models.TransientModel):
_name = 'openacademy.wizard'

def _default_session(self):
return self.env['openacademy.session'].browse(self._context.get('active_id'))

session_id = fields.Many2one('openacademy.session',
string="Session", required=True, default=_default_session)
attendee_ids = fields.Many2many('res.partner', string="Attendees")

openacademy/views/openacademy.xml
parent="openacademy_menu"
action="session_list_action"/>

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


<field name="name">wizard.form</field>
<field name="model">openacademy.wizard</field>
<field name="arch" type="xml">
<form string="Add Attendees">
<group>
<field name="session_id"/>
<field name="attendee_ids"/>
</group>
</form>
</field>
</record>

<act_window id="launch_session_wizard"
name="Add Attendees"
src_model="openacademy.session"
res_model="openacademy.wizard"
view_mode="form"
target="new"
key2="client_action_multi"/>
</data>
</odoo>

Ejercicio
Registro asistentes
Añadir botones para el asistente, y aplicar el procedimiento correspondiente para la adición de los
asistentes a la sesión dada.
openacademy/views/openacademy.xml
<field name="attendee_ids"/>
</group>
<footer>
<button name="subscribe" type="object"
string="Subscribe" class="oe_highlight"/>
or
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>

openacademy/wizard.py
session_id = fields.Many2one('openacademy.session',
string="Session", required=True, default=_default_session)
attendee_ids = fields.Many2many('res.partner', string="Attendees")

@api.multi
def subscribe(self):
self.session_id.attendee_ids |= self.attendee_ids
return {}

Ejercicio
Registro asistentes a varias sesiones
Modificar el modelo de asistente para que los asistentes se pueden registrar en varias sesiones.
openacademy/views/openacademy.xml
<form string="Add Attendees">
<group>
<field name="session_ids"/>
<field name="attendee_ids"/>
</group>
<footer>
<button name="subscribe" type="object"

openacademy/wizard.py
class Wizard(models.TransientModel):
_name = 'openacademy.wizard'

def _default_sessions(self):
return self.env['openacademy.session'].browse(self._context.get('active_ids'))

session_ids = fields.Many2many('openacademy.session',
string="Sessions", required=True, default=_default_sessions)
attendee_ids = fields.Many2many('res.partner', string="Attendees")

@api.multi
def subscribe(self):
for session in self.session_ids:
session.attendee_ids |= self.attendee_ids
return {}

Internacionalización
Cada módulo puede proporcionar a sus propias traducciones en el directorio i18n, por tener archivos con el nombre
LANG.po donde idioma es el código de región para el idioma o el idioma y el país combinación cuando difieren (por
ejemplo pt.po o pt_BR.po). Las traducciones serán cargados automáticamente por Odoo para todos los idiomas
habilitados. Los desarrolladores utilizan siempre Inglés al crear un módulo, a continuación, exportar los términos del
módulo utilizando característica de exportación gettext POT de Odoo ( Ajustes ‣ Las traducciones ‣ Importar /
Exportar ‣ exportación de traducción sin especificar un idioma), para crear el archivo POT plantilla de módulo, y
luego se derivan del traducidos archivos PO. Muchos de IDE tienen plugins o modos de edición y combinación de
archivos / PO POT.
Tip
Los archivos generados por objeto portátil Odoo se publican en Transifex , por lo que es fácil de traducir el software.
|- idea/ # The module directory
|- i18n/ # Translation files
| - idea.pot # Translation Template (exported from Odoo)
| - fr.po # French translation
| - pt_BR.po # Brazilian Portuguese translation
| (...)
Tip
Por defecto exportación POT de Odoo sólo extrae las etiquetas dentro de archivos XML o definiciones de campo en
el interior en el código Python, pero cualquier cadena de Python se puede traducir de esta manera por lo rodea con
la función odoo._() (por ejemplo _("Label"))

Ejercicio
Traducir un módulo
Elegir un segundo idioma para la instalación de Odoo. Traducir el módulo utilizando las facilidades proporcionadas
por Odoo.

1. Crear un directorio openacademy/i18n/


2. Instalar el idioma que desee ( Administración ‣ ‣ Las traducciones cargar una traducción oficial )
3. Sincronizar los términos traducibles ( Administración ‣ ‣ Traducciones Términos de Aplicaciones ‣
Sincronizar Traducciones )
4. Crear una plantilla de archivo de traducción mediante la exportación ( Administración ‣ Traducciones ->
Importar / Exportar ‣ exportación traducción ) sin especificar un idioma, salvo en openacademy/i18n/
5. Crear un archivo de traducción mediante la exportación ( ‣ Las traducciones ‣ Importar / Exportar ‣
exportación Traducción Administración ) y la especificación de un idioma. Guardarlo
en openacademy/i18n/
6. Abra el archivo de traducción exportado (con un editor de texto básico o un dedicado PO-editor de archivos
por ejemplo Poedit y traducir los términos que faltan
7. En models.py, añadir una instrucción de importación para la función odoo._y marcar las cadenas faltantes
como traducible
8. Repita los pasos 3-6

openacademy/models.py
# -*- coding: utf-8 -*-

from datetime import timedelta


from odoo import models, fields, api, exceptions, _

class Course(models.Model):
_name = 'openacademy.course'

default = dict(default or {})

copied_count = self.search_count(
[('name', '=like', _(u"Copy of {}%").format(self.name))])
if not copied_count:
new_name = _(u"Copy of {}").format(self.name)
else:
new_name = _(u"Copy of {} ({})").format(self.name, copied_count)

default['name'] = new_name
return super(Course, self).copy(default)

if self.seats < 0:
return {
'warning': {
'title': _("Incorrect 'seats' value"),
'message': _("The number of available seats may not be negative"),
},
}
if self.seats < len(self.attendee_ids):
return {
'warning': {
'title': _("Too many attendees"),
'message': _("Increase seats or remove excess attendees"),
},
}

def _check_instructor_not_in_attendees(self):
for r in self:
if r.instructor_id and r.instructor_id in r.attendee_ids:
raise exceptions.ValidationError(_("A session's instructor can't be an attendee"))
Reporting (informes)
Los informes impresos
Odoo 8.0 viene con un nuevo motor de informes basado en QWEB , Twitter Bootstrap y wkhtmltopdf .
Un informe es una combinación de dos elementos:
 Una ir.actions.report.xml, por el cual una <report>se proporciona elemento de acceso directo, se establece
varios parámetros básicos para el informe (tipo predeterminado, si el informe debe ser guardados en la base
de datos tras generación, ...)
<report
id="account_invoices"
model="account.invoice"
string="Invoices"
report_type="qweb-pdf"
name="account.report_invoice"
file="account.report_invoice"
attachment_use="True"
attachment="(object.state in ('open','paid')) and
('INV'+(object.number or '').replace('/','')+'.pdf')"
/>
 Un estándar vista QWEB para el informe real:
<t t-call="report.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="report.external_layout">
<div class="page">
<h2>Report title</h2>
</div>
</t>
</t>
</t>

el contexto de representación estándar proporciona un número de elementos, el más


ser importante:

`` docs``
los registros para los que se imprime el informe
`` user``
al usuario imprimir el informe

Dado que los informes son páginas web estándar, están disponibles a través de un parámetros de URL y de
salida pueden ser manipuladas a través de esta URL, por ejemplo, la versión HTML de la factura informe está
disponible a través de http: // localhost: 8069 / informe / html / account.report_invoice / 1 (si account está
instalada) y la versión PDF a través de http: // localhost: 8069 / report / pdf / account.report_invoice / 1 .

Peligro
Si parece que su informe de PDF le faltan los estilos (es decir, el texto aparece pero el estilo / diseño es diferente de
la versión html), probablemente su wkhtmltopdf proceso no puede llegar a su servidor web para descargarlos.
Si se revisa los registros del servidor y ver que los estilos CSS no están siendo descargados al generar un informe
en PDF, con toda seguridad este es el problema.
El wkhtmltopdf proceso va a utilizar el parámetro del sistema web.base.url como la ruta raíz de todos los archivos
vinculados, pero este parámetro se actualiza automáticamente cada vez que el administrador está conectado. Si el
servidor se encuentra protegido por algún tipo de proxy, que no podían ser alcanzable. Puede solucionar este
problema mediante la adición de uno de estos parámetros del sistema:

 report.url, Que apunta a una URL accesible desde el servidor (probablemente http://localhost:8069o algo
similar). Será utilizado sólo para este propósito en particular.
 web.base.url.freeze, Cuando se establece en True, parará las actualizaciones automáticas web.base.url.

Ejercicio
Crear un informe para el modelo de sesión
Para cada sesión, que debe mostrar el nombre de la sesión, su inicio y fin, y la lista de asistentes de la sesión.
openacademy/__manifest__.py
'views/openacademy.xml',
'views/partner.xml',
'views/session_workflow.xml',
'reports.xml',
],
# only loaded in demonstration mode
'demo': [

openacademy/reports.xml
<odoo>
<data>
<report
id="report_session"
model="openacademy.session"
string="Session Report"
name="openacademy.report_session_view"
file="openacademy.report_session"
report_type="qweb-pdf" />

<template id="report_session_view">
<t t-call="report.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="report.external_layout">
<div class="page">
<h2 t-field="doc.name"/>
<p>From <span t-field="doc.start_date"/> to <span t-field="doc.end_date"/></p>
<h3>Attendees:</h3>
<ul>
<t t-foreach="doc.attendee_ids" t-as="attendee">
<li><span t-field="attendee.name"/></li>
</t>
</ul>
</div>
</t>
</t>
</t>
</template>
</data>
</odoo>

Dashboards
Ejercicio
Definir un tablero de instrumentos (dashboard)
Definir un cuadro de mando que contiene la vista de gráfico que ha creado, la vista del calendario de sesiones y una
vista de la lista de los cursos (conmutable a una vista de formulario). Este panel debe estar disponible a través de un
elemento de menú en el menú, y se muestra automáticamente en el cliente Web cuando se selecciona el menú
principal OpenAcademy.
1. Crear un archivo openacademy/views/session_board.xml. Debe contener la opinión de la tarjeta, las
acciones que se hace referencia en ese punto de vista, una acción para abrir el tablero de instrumentos y
una re-definición del elemento del menú principal para añadir la acción salpicadero
Nota
Estilos disponibles del tablero de instrumentos son 1, 1-1, 1-2, 2-1 y 1-1-1

2. Actualización de openacademy/__manifest__.py hace referencia al nuevo archivo de datos.

openacademy/__manifest__.py
'version': '0.1',

# any module necessary for this one to work correctly


'depends': ['base', 'board'],
# always loaded
'data': [

'views/openacademy.xml',
'views/partner.xml',
'views/session_workflow.xml',
'views/session_board.xml',
'reports.xml',
],
# only loaded in demonstration mode

openacademy/views/session_board.xml
<?xml version="1.0"?>
<odoo>
<data>
<record model="ir.actions.act_window" id="act_session_graph">
<field name="name">Attendees by course</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">graph</field>
<field name="view_id"
ref="openacademy.openacademy_session_graph_view"/>
</record>
<record model="ir.actions.act_window" id="act_session_calendar">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">calendar</field>
<field name="view_id" ref="openacademy.session_calendar_view"/>
</record>
<record model="ir.actions.act_window" id="act_course_list">
<field name="name">Courses</field>
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<record model="ir.ui.view" id="board_session_form">
<field name="name">Session Dashboard Form</field>
<field name="model">board.board</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Session Dashboard">
<board style="2-1">
<column>
<action
string="Attendees by course"
name="%(act_session_graph)d"
height="150"
width="510"/>
<action
string="Sessions"
name="%(act_session_calendar)d"/>
</column>
<column>
<action
string="Courses"
name="%(act_course_list)d"/>
</column>
</board>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="open_board_session">
<field name="name">Session Dashboard</field>
<field name="res_model">board.board</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="usage">menu</field>
<field name="view_id" ref="board_session_form"/>
</record>

<menuitem
name="Session Dashboard" parent="base.menu_reporting_dashboard"
action="open_board_session"
sequence="1"
id="menu_board_session" icon="terp-graph"/>
</data>
</odoo>

WebServices
El módulo de servicios web ofrece una interfaz común para todos los de servicios web:

 XML-RPC
 JSON-RPC

Los objetos de negocio también se puede acceder a través del mecanismo de objetos distribuidos. Todos ellos
pueden ser modificados a través de la interfaz de cliente con vistas contextuales.
Odoo es accesible a través de interfaces XML-RPC / JSON-RPC, para los que existen bibliotecas en muchos
idiomas.

XML-RPC Library
El siguiente ejemplo es un programa Python que interactúa con un servidor Odoo con la biblioteca xmlrpclib:

import xmlrpclib

root = 'http://%s:%d/xmlrpc/' % (HOST, PORT)

uid = xmlrpclib.ServerProxy(root + 'common').login(DB, USER, PASS)


print "Logged in as %s (uid: %d)" % (USER, uid)

# Create a new note


sock = xmlrpclib.ServerProxy(root + 'object')
args = {
'color' : 8,
'memo' : 'This is a note',
'create_uid': uid,
}
note_id = sock.execute(DB, uid, PASS, 'note.note', 'create', args)

Ejercicio
Añadir un nuevo servicio al cliente
Escribir un programa Python capaz de enviar peticiones XML-RPC a un PC con Odoo (el suyo o de su
instructor). Este programa debe mostrar todas las sesiones, y su correspondiente número de asientos. También
debe crear una nueva sesión para uno de los cursos.
import functools
import xmlrpclib
HOST = 'localhost'
PORT = 8069
DB = 'openacademy'
USER = 'admin'
PASS = 'admin'
ROOT = 'http://%s:%d/xmlrpc/' % (HOST,PORT)

# 1. Login
uid = xmlrpclib.ServerProxy(ROOT + 'common').login(DB,USER,PASS)
print "Logged in as %s (uid:%d)" % (USER,uid)

call = functools.partial(
xmlrpclib.ServerProxy(ROOT + 'object').execute,
DB, uid, PASS)

# 2. Read the sessions


sessions = call('openacademy.session','search_read', [], ['name','seats'])
for session in sessions:
print "Session %s (%s seats)" % (session['name'], session['seats'])
# 3.create a new session
session_id = call('openacademy.session', 'create', {
'name' : 'My session',
'course_id' : 2,
})

En lugar de utilizar una ID de curso no modificable, el código puede buscar un curso por su nombre:
# 3.create a new session for the "Functional" course
course_id = call('openacademy.course', 'search', [('name','ilike','Functional')])[0]
session_id = call('openacademy.session', 'create', {
'name' : 'My session',
'course_id' : course_id,
})

JSON-RPC Library
El siguiente ejemplo es un programa Python que interactúa con un servidor Odoo con las bibliotecas estándar de
Python urllib2y json:

import json
import random
import urllib2

def json_rpc(url, method, params):


data = {
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": random.randint(0, 1000000000),
}
req = urllib2.Request(url=url, data=json.dumps(data), headers={
"Content-Type":"application/json",
})
reply = json.load(urllib2.urlopen(req))
if reply.get("error"):
raise Exception(reply["error"])
return reply["result"]

def call(url, service, method, *args):


return json_rpc(url, "call", {"service": service, "method": method, "args": args})

# log in the given database


url = "http://%s:%s/jsonrpc" % (HOST, PORT)
uid = call(url, "common", "login", DB, USER, PASS)

# create a new note


args = {
'color' : 8,
'memo' : 'This is another note',
'create_uid': uid,
}
note_id = call(url, "object", "execute", DB, uid, PASS, 'note.note', 'create', args)
Aquí está el mismo programa, el uso de la biblioteca jsonrpclib :
import jsonrpclib

# server proxy object


url = "http://%s:%s/jsonrpc" % (HOST, PORT)
server = jsonrpclib.Server(url)

# log in the given database


uid = server.call(service="common", method="login", args=[DB, USER, PASS])

# helper function for invoking model methods


def invoke(model, method, *args):
args = [DB, uid, PASS, model, method] + list(args)
return server.call(service="object", method="execute", args=args)

# create a new note


args = {
'color' : 8,
'memo' : 'This is another note',
'create_uid': uid,
}
note_id = invoke('note.note', 'create', args)

Estos ejemplos pueden ser fácilmente adaptados de XML-RPC a JSON-RPC.


Nota
Hay una serie de APIs de alto nivel en diversos idiomas para acceder a sistemas Odoo sin explícitamente ir a través
de XML-RPC o JSON-RPC, tales como:

 https://github.com/akretion/ooor
 https://github.com/syleam/openobject-library
 https://github.com/nicolas-van/openerp-client-lib
 http://pythonhosted.org/OdooRPC
 https://github.com/abhishek-jaiswal/php-openerp-lib

[1] es possible disable the automatic creation of some fields (Desactivar la creación automática de algunos
campos)
[2] escribir consultas SQL primas es posible, pero requiere cuidado, ya que no pasa por todos los mecanismos de
autenticación y seguridad Odoo.

También podría gustarte