Professional Documents
Culture Documents
Building a Module
Iniciar / Detener el servidor Odoo
Odoo utiliza un / 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 realiza generalmente en el lado del servidor, aunque las
características del cliente de apoyo (por ejemplo, la nueva representación de datos tales como mapas interactivos)
puede añadirse al cliente.Con el fin de iniciar el servidor, basta con invocar el comando
odoo.py en la consola, la
adición de la ruta completa al archivo si es necesario:
$ ./odoo.py
El servidor es detenido por golpear Ctrl-C en dos ocasiones desde el terminal, o matando el proceso operativo
correspondiente.
Composición de un módulo
1
Imágenes, CSS o archivos javascript usados
por la interfaz web o sitio web
Tip
La mayoría de las opciones de línea de comandos también se pueden configurar mediante
un archivo de
configuración
manifiest
Un módulo Odoo es declarado por su o documentación de manifiesto
. Consulte la la información al respecto.
Un módulo es también un
paquete de Python con un archivo __init__.py, que contiene instrucciones de importación
de varios archivos de Python en el módulo.
Por ejemplo, si el módulo tiene un solo mymodule.py archivo __init__.py podría contener:
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 contienen simplemente código comentado o XML. El uso de la mayor parte de esos
archivos se explicará a lo largo de este tutorial.
Ejercicio
Creación del módulo
Utilice la línea de comandos anterior para crear un módulo vacío openacademy, e instalarlo en Odoo.
openacademy/__openerp__.py
2
'summary': """Manage trainings""",
'description': """
Open Academy module for managing trainings:
- training courses
- training sessions
- attendees registration
""",
# always loaded
'data': [
# 'security/ir.model.access.csv',
'templates.xml',
],
# only loaded in demonstration mode
'demo': [
'demo.xml',
],
}
openacademy/__init__.py
3
openacademy/controllers.py
# class Openacademy(http.Controller):
# @http.route('/openacademy/openacademy/', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/openacademy/openacademy/objects/', auth='public')
# def list(self, **kw):
# return http.request.render('openacademy.listing', {
# 'root': '/openacademy/openacademy',
# 'objects': http.request.env['openacademy.openacademy'].search([]),
# })
# @http.route('/openacademy/openacademy/objects/<model("openacademy.openacademy"):obj>/',
auth='public')
# def object(self, obj, **kw):
# return http.request.render('openacademy.object', {
# 'object': obj
# })
openacademy/demo.xml
<openerp>
<data>
<!-- -->
<!-- <record id="object0" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 0</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object1" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 1</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object2" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 2</field> -->
4
<!-- </record> -->
<!-- -->
<!-- <record id="object3" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 3</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object4" model="openacademy.openacademy"> -->
<!-- <field name="name">Object 4</field> -->
<!-- </record> -->
<!-- -->
</data>
</openerp>
openacademy/models.py
# class openacademy(models.Model):
# _name = 'openacademy.openacademy'
# name = fields.Char()
openacademy/security/ir.model.access.csv
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_openacademy_openacademy,openacademy.openacademy,model_openacademy_openacademy,,1,0,0,0
openacademy/templates.xml
<openerp>
<data>
<!-- <template id="listing"> -->
<!-- <ul> -->
<!-- <li t-foreach="objects" t-as="object"> -->
<!-- <a t-attf-href="{{ root }}/objects/{{ object.id }}"> -->
<!-- <t t-esc="object.display_name"/> -->
<!-- </a> -->
<!-- </li> -->
<!-- </ul> -->
<!-- </template> -->
5
<!-- <template id="object"> -->
<!-- <h1><t t-esc="object.display_name"/></h1> -->
<!-- <dl> -->
<!-- <t t-foreach="object._fields" t-as="field"> -->
<!-- <dt><t t-esc="field"/></dt> -->
<!-- <dd><t t-esc="object[field]"/></dd> -->
<!-- </t> -->
<!-- </dl> -->
<!-- </template> -->
</data>
</openerp>
Mapeo objeto-relacional
Un componente clave de Odoo es el ORM capa. Esta capa evita tener que escribir la mayoría de SQL con la mano y
proporciona servicios de extensibilidad y seguridad
[2]
.
Los objetos de negocio son declarados como clases de Python extendiendo el
Modelo que los integra en el sistema de
persistencia automatizado.
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:
Los campos que se utilizan para definir lo que el modelo puede almacenar y dónde. Los campos se definen como
atributos de la clase del modelo:
class LessMinimalModel(models.Model):
_name = 'test.model2'
name = fields.Char()
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:
6
name = field.Char(required=True)
Algunos atributos están disponibles en todos los campos, estos son los más comunes:
Campos simples
Hay dos grandes categorías de campos: campos "simples", que son valores atómicos almacenados directamente en la
mesa del modelo y los campos "relacionales" que unen los registros (del mismo modelo o de diferentes modelos).
Campos reservados
Odoo crea algunos campos en todos los modelos
[1] . Estos campos son gestionados por el sistema y no se deben
escribir. Se pueden leer si es útil o necesario:
id (Id)
el identificador único para el registro de su modelo
create_date (
Datetime
)
fecha de creación del registro
create_uid (
Many2one
)
usuario que creó el registro
write_date (
Datetime
)
última fecha de modificación del registro
write_uid (
Many2one
)
usuario que modificó por última vez el registro
Campos especiales
Por defecto, Odoo también requiere un campo nombre en todos los modelos para diversas búsquedas y mostrar
comportamientos. El campo que se utiliza para estos fines puede ser anulado por el establecimiento
_rec_name
.
7
Ejercicio
Definir un modelo
Definir un nuevo modelo Curso en el módulo openacademy. Un curso tiene un título y una descripción. Los cursos
deben tener un título.
openacademy/models.py
Tip
Existen algunos módulos únicamente para agregar datos en Odoo
<openerp>
<data>
<record model="{model name}" id="{record identifier}">
<field name="{a field name}">{a value}</field>
</record>
</data>
<openerp>
identificador externo
➢ id es un , permite referir 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.
8
Los archivos de datos tienen que ser declarados en el archivo de manifiesto para ser cargado, pueden ser
declaradas en el 'data' list (always loaded) (siempre cargado) o en la 'demostración' lista (sólo cargan en modo
de demostración).
Ejercicio
Definir los datos de demostración
Crear datos de demostración que llenan el modelo Cursos con algunos cursos de demostración.
openacademy/demo.xml
<openerp>
<data>
<record model="openacademy.course" id="course0">
<field name="name">Course 0</field>
<field name="description">Course 0's description
Acciones y Menús
Son registros regulares en la base de datos, por lo general a través de los archivos de datos declarados. Las
acciones pueden ser desencadenados por tres vías:
9
Debido a que los menús son algo complejos para declarar hay un acceso directo <menuitem> declarado en
ir.ui.menu para conectarlo a la acción correspondiente con más facilidad.
Tip
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 el menú se puede crear.
Ejercicio
Definir nuevas entradas de menú
Definir nuevas entradas de menú para acceder a los cursos y sesiones en virtud de la entrada del menú
OpenAcademy. Un usuario debe ser capaz de
openacademy/__openerp__.py
'data': [
# 'security/ir.model.access.csv',
'templates.xml',
'views/openacademy.xml',
],
# only loaded in demonstration mode
'demo': [
10
openacademy/views/openacademy.xml
11
Vistas básicas
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 solicitadas 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
identificación. Para las solicitudes de genéricos, 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).
Ver herencia
permite alterar vistas declarados en otra parte (la adición o eliminación del contenido).
Una vista es declarado como un registro del modelo ir.ui.view . El tipo de vista está implícita en el elemento raíz del
campo arch:
Tip El contenido de la vista es XML. El campo arch por lo tanto debe ser declarada como type = "xml" para que sea
analizado correctamente.
Vistas de árbol
Los 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 para mostrar en la tabla (cada campo como una columna):
Vistas de formulario
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 (gruops, notebooks) y elementos interactivos (buttons y fields):
12
<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>
<notebook colspan="4">
<page string="Description">
<field name="description" nolabel="1"/>
</page>
</notebook>
<field name="state"/>
</group>
</form>
Ejercicio
Personalice el formulario utilizando XML
Crea tu propia vista de formulario para el objeto del curso. Datos muestra debe ser: el nombre y la descripción del
curso.
openacademy/views/openacademy.xml
13
<group>
<field name="name"/>
<field name="description"/>
</group>
</sheet>
</form>
</field>
</record>
Ejercicio
Notebooks
En la vista de formulario de Curso, poner el campo de descripción bajo una pestaña, de manera que será más fácil para
agregar otras fichas más tarde, que contiene información adicional. Modificar la vista de formulario de 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>
14
Las vistas de formulario también pueden usar HTML plano para diseños más flexibles:
Vistas de búsquedas
Personaliza el campo de búsqueda asociado a la vista de la lista (y otras vistas agregadas). Su elemento raíz es
<search> y se compone de campos que definen porqué campos se pueden buscar:
<search>
<field name="name"/>
<field name="inventor_id"/>
</search>
Si no existe una vista de búsqueda para el modelo, Odoo genera uno que sólo permite la búsqueda por el campo name
Ejercicio
Buscar cursos
openacademy/views/openacademy.xml
15
</field>
</record>
Ejercicio
Crear un modelo de sesión
Para el módulo de la Academia Americana, 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
sillas. 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ú.
openacademy/models.py
16
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
17
<menuitem id="session_menu" name="Sessions"
parent="openacademy_menu"
action="session_list_action"/>
</data>
</openerp>
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 resulta en los dígitos de los números antes de la coma es de un
máximo de 4
Campos relacionales
Campos relacionales vinculan registros, ya sea del mismo modelo (jerarquías) o entre diferentes modelos. Los tipos de
campo relacionales son:
One2many(other_model, related_field)
Una relación virtual, inversa de una
Many2one
. Un
one2many se comporta como contenedor de los registros, el
acceso se traduce en un conjunto de registros (posiblemente vacía):
Nota
Debido a que un
one2many es una relación virtual, debe haber un campo
Many2one en el other_model, y su nombre
debe ser related_field
Many2many(other_model)
Relación múltiple bidireccional, cualquier registro en un lado puede estar relacionada con cualquier número de
registros en el otro lado. Se comporta como un contenedor de archivos, acceder a él también se traduce en una serie
posiblemente vacía de registros:
Ejercicio
Relaciones Many2one
18
El uso de un many2one, modifique los modelos del curso y Sesión para reflejar su relación con otros modelos:
➢ Un curso tiene un usuario responsable; 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 a un curso ; el valor de ese campo es un registro del modelo openacademy.course y se
requiere.
➢ Adaptar las vistas.
1. Añadir las pertinentes Many2one campos a los modelos, y
2. añadirlos en las vistas.
openacademy/models.py
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")
openacademy/views/openacademy.xml
<sheet>
<group>
<field name="name"/>
<field name="responsible_id"/>
</group>
<notebook>
<page string="Description">
19
</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>
20
<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
Relaciones One2Many inversas
Utilizando la inversa one2many, modificar los modelos para reflejar la relación entre los cursos y sesiones.
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>
21
Ejercicio
Múltiples relaciones Many2Many
Utilizando el Many2Many, modificar el período de sesiones de 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.
openacademy/models.py
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 ofrece dos mecanismos de herencia para extender un modelo existente de forma modular. El primer
mecanismo de herencia permite a un módulo modificar el comportamiento de un modelo definido en otro
módulo:
22
El segundo mecanismo de herencia (delegación) permite vincular cada registro de un modelo para un registro en
un modelo de los padres, y proporciona un acceso transparente a los campos del registro padre.
Herencia de vista
En lugar de modificar las vistas existentes en su lugar (sobrescribiendolas), Odoo ofrece herencia de vistas donde
se aplica a los hijos vistas "de extensión" en la parte superior de la vista de la raíz, y pueden añadir o eliminar el
contenido de sus padres.
Una vista de extensión hace referencia a su padre con el campo inherit_id, y en lugar de una sola vista de su
campo arch se compone de un número cualquiera de elementos XPath de selección y alterando el contenido de
su vista padre:
23
<!-- find field description inside tree, and add the field
idea_ids after it -->
<xpath expr="/tree/field[@name='description']" position="after">
<field name="idea_ids" string="Number of ideas"/>
</xpath>
</field>
</record>
expr
Un expresión
XPath para seleccionar un solo elemento en la vista padre. Plantea un error si coincide con
ningún elemento o más de uno
position
Operación de aplicar al elemento coincidente:
inside
anexa XPath en el extremo del elemento emparejado
replace
reemplaza el elemento emparejado por el xpath
before
inserta el xpath como un hermano antes de que el elemento coincidente
after
inserta los XPaths como un hermano después del elemento emparejado
attributes
altera los atributos del elemento coincidente utilizando atributos especiales en el xpath
Ejercicio
Alterar el contenido existente
➢ Utilizando el modelo de herencia, modificar el vigente modelo Partner para añadir un campo booleano
instructor, y un campo de Many2Many que corresponde a la relación de sesión-partner
➢ Usar la vista herencia, visualizar estos campos en la vista de formulario del partner
Nota Esta es la oportunidad para introducir el modo de programador para inspeccionar la vista, encontrará su
ID externo y el lugar para poner el nuevo campo.
24
openacademy/__init__.py
openacademy/__openerp__.py
# 'security/ir.model.access.csv',
'templates.xml',
'views/openacademy.xml',
'views/partner.xml',
],
# only loaded in demonstration mode
'demo': [
openacademy/partner.py
class Partner(models.Model):
_inherit = 'res.partner'
# Add a new column to the res.partner model, by default partners are not
# instructors
instructor = fields.Boolean("Instructor", default=False)
session_ids = fields.Many2many('openacademy.session',
string="Attended Sessions", readonly=True)
openacademy/views/partner.xml
25
<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>
</openerp>
Dominios
En Odoo,
Dominios son valores que codifican las condiciones en los registros. Un dominio es una lista de los
criterios utilizados para seleccionar un subconjunto de los registros de un modelo. Cada criterio es un triple con
un nombre de campo, un operador y un valor.
Por criterios predeterminados se combinan con una Y implícita. Los operadores lógicos Y (AND), | (OR) y ! (NO)
se puede 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 los servicios
O tienen un precio unitario que es
NO entre
1000 y 2000 ":
26
['|',
('product_type', '=', 'service'),
'!', '&',
('unit_price', '>=', 1000),
('unit_price', '<', 2000)]
Un parámetro se puede añadir a los campos relacionales para limitar registros válidos para la relación cuando se
trata de seleccionar registros en la interfaz del cliente.
Ejercicio
Dominios sobre campos relacionales
Al seleccionar el instructor para una reunión, sólo los instructores (Partner con instructor establecido en True )
deben ser visibles.
openacademy/models.py
Nota Un dominio declarado como una lista literal se evalúa del lado del servidor y no puede referir valores
dinámicos en el lado derecho, un dominio declarado como una cadena se evalúa el lado del cliente y permite
nombres de campo en la parte derecha.
Ejercicio
Dominios más complejos
Crear una nueva categoria de partner Teacher / Level 1 and Teacher / Level 2. El instructor para una sesión puede
ser un instructor o un profesor (de cualquier nivel).
27
openacademy/models.py
openacademy/views/partner.xml
28
Nota 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 de Python estándar en las colecciones, como
len(self) y
iter(self)
,
además de operaciones de conjuntos adicionales como recs1 + recs2
.
La iteración sobre
self da los registros uno por uno, donde cada registro es en sí mismo una colección de tamaño
1. Puede acceder/asignar campos en los registros individuales usando la notación de puntos, como record.name
.
import random
from openerp import models, fields
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))
@api.one
def _compute_name(self):
self.name = str(random.randint(1, 1e6))
Dependencias
El valor de un campo calculado generalmente depende de los valores de otros campos en el registro
computarizado. El ORM espera que el desarrollador especifique esas dependencias en el método compute con el
decorador
depends()
. Las dependencias dadas son utilizadas por el ORM para activar el recálculo del campo
siempre que algunas de sus dependencias se han modificado:
class ComputedModel(models.Model):
_name = 'test.computed'
name = fields.Char(compute='_compute_name')
value = fields.Integer()
29
@api.one
@api.depends('value')
def _compute_name(self):
self.name = "Record with value %s" % self.value
Ejercicio
Campos calculados
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.one
@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
if not self.seats:
self.taken_seats = 0.0
else:
self.taken_seats = 100.0 * len(self.attendee_ids) / self.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"/>
30
<tree string="Session Tree">
<field name="name"/>
<field name="course_id"/>
<field name="taken_seats" widget="progressbar"/>
</tree>
</field>
</record>
Valores predeterminados
A cualquier campo se puede dar un valor predeterminado. En la definición de campo, añada la opción default=X
donde X es o bien un valor Python literal (boolean, integer, float, string), o una función que toma un conjunto de
registros y devuelve 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 la petición y otras cosas útiles:
Ejercicio
Objetos activos – Valores predeterminados
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),
31
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 que hacen un campo invisible cuando un campo active se establece en
False.
Onchange
El mecanismo de "onchange" proporciona una forma para actualizar la interfaz de cliente cada vez que el usuario
ha 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_price y price, y desea actualizar el
precio en el formulario cuando cualquiera de los otros campos se modifica. Para lograr esto, definir un método
donde self representa el registro en la vista form, y se declara con
onchange() para especificar en qué campo
tiene que ser activado. Cualquier cambio que realice en self se reflejará en el formulario.
# 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",
}
}
32
Para los campos calculados, el comportamiento onchange está incorporado como se puede ver jugando con la
Session form: cambiar el número de asientos o los participantes, y la progressbar taken_seats se actualiza
automáticamente.
Ejercicio
Advertencia
Añadir un onchange explícito para advertir sobre los valores no válidos, al igual que un número negativo de
asientos, o más participantes que los asientos.
openacademy/models.py
self.taken_seats = 0.0
else:
self.taken_seats = 100.0 * len(self.attendee_ids) / self.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 Modelo
Odoo ofrece dos formas de configurar invariantes verificadas de forma automática:
Python constraints y
SQL
constraints
.
33
from openerp.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 una restriccion Python
Añadir una restricción que comprueba que el instructor no está presente en los asistentes de su propia sesión.
openacademy/models.py
class Course(models.Model):
_name = 'openacademy.course'
@api.one
@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
if self.instructor_id and self.instructor_id in self.attendee_ids:
raise exceptions.ValidationError("A session's instructor can't be an attendee")
Ejercicio
Añadir restricciones SQL
1. Compruebe que la descripción del curso y el título del curso son diferentes
2. Hacer el nombre del curso único
34
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
Añadir una opción duplicate
Desde que añadimos una restricción para el nombre del curso singularidad, no es posible utilizar la función
"duplicar" más (Formulario ‣Duplicar).
Vuelva a aplicar su propio método de "copiar", 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.one
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)
35
_sql_constraints = [
('name_description_check',
'CHECK(name != description)',
Vistas avanzadas
Vistas árbol
Las vistas de árbol pueden tomar atributos complementarios para personalizar aún más su comportamiento:
colors
asignaciones de colores a las condiciones. Si la condición se evalúa como True, el color correspondiente se
aplica a la fila:
<tree string="Idea Categories" colors="blue:state=='draft';red:state=='trashed'">
<field name="name"/>
<field name="state"/>
</tree>
Las cláusulas están separados por (;) , el color y el estado están separados por (:) .
editable
Cualquier "top" o "bottom". Hace que la vista de árbol editable 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
Coloración de las listas
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.
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" colors="#0000ff:duration<5;red:duration>15">
<field name="name"/>
<field name="course_id"/>
<field name="duration" invisible="1"/>
<field name="taken_seats" widget="progressbar"/>
</tree>
</field>
36
Calendarios
Muestra los registros como los eventos del calendario. Su elemento raíz es <calendar> y sus atributos más comunes:
color
Los colores se distribuyen automáticamente a los eventos, pero los acontecimientos en el mismo segmento de
color (registros que tienen el mismo valor para su campocolor) se les dará el mismo color.
date_start
campo de registro de la celebración del inicio de fecha / hora para el evento
date_stop (optional)
campo de registro de la celebración de la final de fecha / hora para el evento
string
campo (para definir la etiqueta para cada evento de calendario)
Ejercicio
Vista calendario
Agregar una vista de calendario para el modelo de sesión que permite al usuario ver los eventos.
1. Agregar un campo
end_date
calculado a partir de
start_date
y
duration
Tip
La función inversa hace que el campo se pueda escribir, y permite mover las sesiones (a través de arrastrar y
soltar) en la vista del calendario
openacademy/models.py
class Course(models.Model):
37
attendee_ids = fields.Many2many('res.partner', string="Attendees")
@api.one
@api.depends('seats', 'attendee_ids')
}
@api.one
@api.depends('start_date', 'duration')
def _get_end_date(self):
if not (self.start_date and self.duration):
self.end_date = self.start_date
return
@api.one
def _set_end_date(self):
if not (self.start_date and self.end_date):
return
@api.one
@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
if self.instructor_id and self.instructor_id in self.attendee_ids:
openacademy/views/openacademy.xml
38
</field>
</record>
Vistas de búsqueda
En vista búsqueda, los elementos
<field> pueden tener un filter_domain que anula el dominio generado para buscar
en el campo dado. En el dominio dado,
self representa los valores ingresados por el usuario. En el ejemplo siguiente,
se utiliza para buscar tanto en los campos nombre como descripción.
Search views tambien 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
<search string="Ideas">
<field name="name"/>
39
<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'}"/>
</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 action.
La acción también puede establecer valores predeterminados para los campos de búsqueda a través de su campo
context
: las claves de context, search_default_field_name inicializará field_name con el valor proporcionado. Los
filtros de búsqueda deben tener un opcional @name por defecto y comportarse como booleanos ( que sólo pueden
ser activadas por defecto ).
Ejercicio
Vistas de búsqueda
1. Agregue 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 agrupar los cursos por 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="Responsable"
context="{'group_by': 'responsible_id'}"/>
</group>
</search>
</field>
</record>
40
<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
Los gráficos de barras horizontales suelen utilizar para mostrar la planificación y el avance del proyecto, su elemento
raíz es
<gantt>
.
Ejercicio
Diagrama de Gantt
Añadir un diagrama de Gantt que permite al usuario ver la programación de sesiones relacionado con el módulo
openacademy. Las sesiones deben ser agrupadas por el instructor.
openacademy/models.py
@api.one
@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
self.duration = (end_date - start_date).days + 1
41
@api.one
@api.depends('duration')
def _get_hours(self):
self.hours = self.duration * 24
@api.one
def _set_hours(self):
self.duration = self.hours / 24
@api.one
@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
if self.instructor_id and self.instructor_id in self.attendee_ids:
openacademy/views/openacademy.xml
</field>
</record>
42
Vistas de gráfico
Permiten visión general y análisis de los modelos agregados, su elemento raíz es
<graph>
. Tienen 4 modos de
visualización, el modo por defecto se selecciona mediante el atributo
@type
.
Pivot
una tabla multidimensional, permite la selección de contribuyentes y dimensiones para obtener el conjunto de
datos agregados derecha antes de pasar a una visión más gráfica
Bar (default)
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 de
@stacked="True"
en el
<graph>
Line
Gráfico de líneas de 2 dimensiones
Pie
Pastel de 2 dimensiones
Las vistas de gráficos contienen <field > con un atributo type obligatorio tomar los valores:
row (default)
el campo debe ser agregada por defecto
measure
el campo debe ser agregado en lugar de agruparse
Nota
Las vistas de gráfico pueden realizar agregaciones en los valores de base de datos, que no funcionan con
campos que no son almacenados computadas
Ejercicio
Vista de gráfico
Añadir una vista de gráfico en el objeto Session que se muestra, para cada curso, el número de asistentes bajo la forma
de un gráfico de barras.
43
openacademy/models.py
attendees_count = fields.Integer(
string="Attendees count", compute='_get_attendees_count', store=True)
@api.one
@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
self.duration = self.hours / 24
@api.one
@api.depends('attendee_ids')
def _get_attendees_count(self):
self.attendees_count = len(self.attendee_ids)
@api.one
@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
if self.instructor_id and self.instructor_id in self.attendee_ids:
openacademy/views/openacademy.xml
</record>
44
<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>
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 cartas posiblemente agrupados en columnas. Cada carta representa un
registro, y cada columna los valores de un campo de agregación.
Por ejemplo, las tareas del proyecto pueden ser organizados por etapa (cada columna es una etapa), o por
responsable (cada columna es un usuario), y así sucesivamente.
Las viistas Kanban definen la estructura de cada tarjeta como una mezcla de elementos de formulario (incluyendo
HTML básico) y
QWeb
.
Ejercicio
Vista Kanban
Añadir un punto de vista Kanban que muestra sesiones agrupadas por supuesto (columnas son los cursos).
1. Añadir un campo entero color para el modelo de sesión
2. Añadir una vista kanban y actualizar la acción
openacademy/models.py
45
openacademy/views/openacademy.xml
</record>
46
<field name="duration"/>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
Workflows
Los flujos de trabajo son modelos asociados a los 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
Añadir un campo de estado para el modelo de sesión. Se utiliza para definir un flujo de trabajo -ish.
Una sesion puede tener tres estados posibles : Borrador ( predeterminado) , confirmados y hecho.
En el form de sesiones, agregar un campo (sólo lectura ) para visualizar el estado , y los botones para cambiarlo . Las
transiciones válidas son:
47
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.one
def action_draft(self):
self.state = 'draft'
@api.one
def action_confirm(self):
self.state = 'confirmed'
@api.one
def action_done(self):
self.state = 'done'
@api.one
@api.depends('seats', 'attendee_ids')
def _taken_seats(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"/>
48
<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 asociados con cualquier objeto en Odoo, y son totalmente personalizable. 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
transiciones (condiciones) se declaran como registros XML, como de costumbre. Las fichas que navegan en los flujos
de trabajo se denominan elementos de trabajo.
Nota Un flujo de trabajo asociado a un modelo sólo se crea cuando se crean los registros de la modelo. Por lo tanto
no hay ninguna instancia de flujo de trabajo asociado con casos de sesión creadas antes de la definición del flujo de
trabajo
Ejercicio
Workflow
Reemplace el flujo de trabajo de sesiones ad-hoc por un flujo de trabajo real. Transformar la vista de formulario de
sesiones por lo que sus botones puedan llamar el flujo de trabajo en lugar de los métodos del modelo.
openacademy/__openerp__.py
'templates.xml',
'views/openacademy.xml',
'views/partner.xml',
'views/session_workflow.xml',
],
# only loaded in demonstration mode
'demo': [
49
openacademy/models.py
('draft', "Draft"),
('confirmed', "Confirmed"),
('done', "Done"),
])
@api.one
def action_draft(self):
openacademy/views/openacademy.xml
openacademy/views/session_workflow.xml
<openerp>
<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>
50
</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>
</openerp>
Tip
Con el fin de comprobar si las instancias del flujo de trabajo se crean correctamente junto a las sesiones, vaya a
Settings ‣Technical ‣Workflows ‣Instances
51
Ejercicio
Transiciones automáticas
Transición automática de sesiones de Borrador a Confirmado, cuando más de la mitad de los asientos de la sesión son
reservados.
openacademy/views/session_workflow.xml
Ejercicio
Acciones del servidor
Reemplace 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 creadas por completo de la interfaz de usuario.
openacademy/views/session_workflow.xml
<field name="on_create">True</field>
</record>
52
<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>
53
Seguridad
Mecanismos de control de acceso se deben configurar para lograr una política de seguridad coherente.
Derechos de acceso
Los derechos de acceso se definen como los registros del modelo ir.model.access. Cada derecho de acceso está
asociado a un modelo, un grupo ( o ningún grupo de acceso global ), y un conjunto de permisos: leer, escribir, crear,
desvincular. Estos derechos de acceso suelen ser creados por un archivo CSV lleva el 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
Agregar el control de acceso a través de la interfaz de OpenERP
Crear un nuevo usuario " John Smith ". A continuación, cree un grupo " OpenAcademy / Sesión Lee " con acceso de
lectura a la modelo de sesión.
Ejercicio
Agregar el control de acceso a través de los archivos de datos en el módulo
Usando los archivos de datos,
54
1. Crear un nuevo archivo
openacademy/security/security.xml
para mantener OpenAcademy Manager group
2. Editar el archivo
openacademy/security/ir.model.access.csv
con derecho de acceso a los modelos
3. Actualizar
openacademy/__openerp__.py
para añadir los nuevo archivos de datos
openacademy/__openerp__.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
<openerp>
<data>
<record id="group_manager" model="res.groups">
<field name="name">OpenAcademy / Manager</field>
</record>
</data>
</openerp>
Reglas de registro
Una regla registro restringe los derechos de acceso a un subconjunto de registros del modelo dado. Una regla es un
registro del modelo ir.rule , y se asocia a un modelo, un número de grupos (campo Many2Many ), los permisos a los
que se aplica la restricción, y un dominio. El dominio especifica a que registro los derechos de acceso son limitados.
55
He aquí un ejemplo de una regla que impide la eliminación de los cables que no están en estado de cancelar. Observe
que el valor de los grupos de campos debe seguir la misma convención como el método write() de la ORM.
Ejercicio
Regla de registro
Añadir una regla de registro para el modelo Curso y el grupo " OpenAcademy / Manager" , que restringe escribir y
desvincular accesos al responsable de un curso. Si un curso no tiene ningun responsable, todos los usuarios del grupo
debe ser capaz de modificarlo. Crear una nueva regla en openacademy/security/security.xml:
openacademy/security/security.xml
56
Wizards
Wizards describen sesiones interactivas con el usuario (o cuadros de diálogo ) a través de formularios dinámicos. Un
wizard no es más que un modelo que amplía la clase TransientModel en lugar de Modelo. La clase TransientModel
extiende modelo y reutiliza todos sus mecanismos existentes, con las siguientes particularidades:
➢ Wizard de registros no están destinados a ser persistente; que se eliminan automáticamente de la base de
datos después de un cierto tiempo. Es por eso que se llaman transitoria.
➢ Wizard de modelos no requieren derechos de acceso explícitos: los usuarios tienen todos los permisos en
los registros del wizard.
➢ Wizard de registros pueden referirse a los registros regulares o registros asistente a través de campos
many2one, pero los registros regulares no pueden hacer referencia a los registros del asistente a través de
un campo de many2one.
Queremos crear un wizard que permite a los usuarios crear asistentes para una sesión en particular, o para obtener
una lista de las sesiones a la vez.
Ejercicio
Definir el aistente
Crear un asistente de modelo con una relación many2one con el modelo de sesión y una relación Many2Many con el
modelo partner. Añadir un nuevo archivo openacademy/wizard.py:
openacademy/__init__.py
openacademy/wizard.py
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")
57
Lanzamiento de wizards
Los wizards son lanzados por
ir.actions.act_window
, con el campo
target seteado con el valor
new
. Este último se
abre la vista del asistente en una ventana emergente. La acción puede ser provocada por un elemento de menú.
Hay otra manera de iniciar el asistente: utilizar un registro
ir.actions.act_window como el anterior, pero con un
campo extra
src_model que especifica en el contexto de qué modelo de la acción 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 el tag
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"/>
Los wizards usan visitas regulares y sus botones pueden utilizar el atributo
special="cancel" para cerrar la ventana del
asistente sin guardar.
Ejercicio
Lanzar el wizard
1. Definir una vista form para el wizard.
2. Añadir la acción para lanzar en el contexto del modelo de Sesión
3. Definir un valor por defecto para el campo session en el wizard; usar el parámetro de contexto
self._context
para recuperar la sesion 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")
58
openacademy/views/openacademy.xml
parent="openacademy_menu"
action="session_list_action"/>
<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>
</openerp>
Ejercicio
Registrar asistentes
Añadir un boton para el wizard, e implementar el correspondiente método para añadir los asistentes a la session dada.
openacademy/views/openacademy.xml
<field name="attendee_ids"/>
</group>
<footer>
<button name="subscribe" type="object"
string="Subscribe" class="oe_highlight"/>
59
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
Registrar los asistentes a varias sesiones
Modificar el wizard del modelo para que los asistentes puedan ser registrados en varias sesiones.
openacademy/views/openacademy.xml
openacademy/wizard.py
class Wizard(models.TransientModel):
_name = 'openacademy.wizard'
def _default_sessions(self):
60
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 {}
Internationalización
Cada módulo puede proporcionar sus propias traducciones en el directorio i18n, teniendo archivos denominados
LANG.po donde LANG es el código de configuración regional para la lengua, o la combinación de idioma y país cuando
difieren (por ejemplo pt.po o pt_BR.po). Las traducciones se cargarán automáticamente por Odoo para todos los
idiomas habilitados.
Los desarrolladores siempre utilizan Inglés al crear un módulo, a continuación, exportar los términos del módulo
utilizando característica de exportación POT gettext de Odoo (
Settings ‣Translations ‣Import/Export ‣Export
Translation sin especificar un idioma), para crear el archivo POT plantilla de módulo, y luego derivan del traducidos
archivos PO. Muchos IDE tienen plugins o modos de edición y fusión de archivos PO/POT.
Tip
El formato gettext GNU (Portable Object) utilizado por Odoo está integrado en LaunchPad, lo que es una plataforma
de traducción colaborativa en línea.
Tip
61
Por defecto POT exportación de Odoo sólo extrae etiquetas dentro de archivos XML o definiciones de campo dentro
de código Python, pero ningun string de Python se puede traducir de esta manera por lo rodeándolo con la función de
OpenERP ._ ( ) (por ejemplo _ ( "Label" ) )
Ejercicio
Traducir un módulo
Elegir un segundo idioma para la instalación Odoo. Traducir el módulo usando las facilidades proporcionadas por
Odoo.
1. Crear un directorio
openacademy/i18n/
2. Instalar cualquier idioma que quiera (
Administration ‣Translations ‣Load an Official Translation
)
3. Sincronizar los términos traducibles (
Administration ‣Translations ‣Application Terms ‣Synchronize
Translations
)
4. Crear un archivo de traducción de plantilla para exportar (
Administration ‣Translations -> Import/Export ‣
Export Translation
) sin especificar un idioma, guardarlo en
openacademy/i18n/
5. Crear un archivo de traducción para exportar (
Administration ‣Translations ‣Import/Export ‣Export
Translation
) y especificar un idioma. Guardarlo en
openacademy/i18n/
6. Abrir el archivo de traducción exportado (con un editor de texto básico o un editor de archivos PO dedicado
como por ejemplo
POEdit
y traducir los términos que faltan.
7. En
models.py
, añadir una sentencia import para la función
openerp._ y marcar las cadenas traducibles
como desaparecidas
8. Repetir los pasos del 3 al 6
openacademy/models.py
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:
62
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"),
},
}
@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
if self.instructor_id and self.instructor_id in self.attendee_ids:
raise exceptions.ValidationError(_("A session's instructor can't be an attendee"))
Reporting
Reportes 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:
➢ <report
id="account_invoices"
model="account.invoice"
string="Invoices"
63
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')"
/>
➢ <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 una serie de elementos, siendo los más importantes:
`` 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 URL y la salida de parámetros
pueden ser manipulados a través de esta URL, por ejemplo, está disponible la versión HTML del informe de facturas a
través de
http://localhost:8069/report/html/account.report_invoice/1 (si
account está instalado) y la versión PDF a
través de
http://localhost:8069/report/pdf/account.report_invoice/1
.
Ejercicio
Crear un reporte para el modelo Session
Para cada sesión, debe mostrar el nombre de la sesión, el inicio y el final, y la lista de los asistentes a la sesión.
openacademy/__openerp__.py
'views/openacademy.xml',
'views/partner.xml',
64
'views/session_workflow.xml',
'reports.xml',
],
# only loaded in demonstration mode
'demo': [
openacademy/reports.xml
<openerp>
<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>
</openerp>
Dashboards
65
Ejercicio
Definir un Dashboard
Definir un panel que contiene la vista de gráfico que ha creado, la vista del calendario de sesiones y una vista de 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 muestran 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 vista tabla, las acciones que
se hace referencia en esta vista, una acción para abrir el tablero de instrumentos y una re-definición del
elemento del menú principal para agregar la acción dashboard
Nota
Los estilos de dashboard disponibles son
1
,
1-1
,
1-2
,
2-1
y
1-1-1
2. Actualizar
openacademy/__openerp__.py
para hacer referencia al nuevo archivo de datos
openacademy/__openerp__.py
'version': '0.1',
# 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"?>
<openerp>
<data>
<record model="ir.actions.act_window" id="act_session_graph">
66
<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"/>
67
</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>
</openerp>
WebServices
El módulo de servicios web ofrece una interfaz común para todos los 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 de Python que interactúa con un servidor Odoo con la biblioteca xmlrpclib:
68
import xmlrpclib
Ejercicio
Añadir un nuevo servicio al cliente
Escriba un programa Python capaz de enviar peticiones XML-RPC a un PC con Odoo (el suyo o el de su instructor). Este
programa debe mostrar todas las sesiones, y su correspondiente número de sillas. También debe crear una nueva
sesión de 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)
69
sessions = call('openacademy.session','search_read', [], ['name','seats'])
for session in sessions:
print "Session %s (%s seats)" % (session['name'], session['seats'])
En lugar de utilizar un identificador de curso no modificable, el código puede buscar un curso por nombre:
JSON-RPC Library
El siguiente programa Python interactúa con un servidor Odoo con las bibliotecas estándar urllib2 y JSON:
import json
import random
import urllib2
70
def call(url, service, method, *args):
return json_rpc(url, "call", {"service": service, "method": method, "args": args})
import jsonrpclib
71