📚 Resúmenes Teóricos
Conceptos clave para el examen práctico
🏗️ Modelos en Odoo
Estructura básica de un modelo
Un modelo en Odoo representa una tabla en la base de datos. Siempre hereda de models.Model.
from odoo import models, fields, api
class MiModelo(models.Model):
# Nombre técnico del modelo (nombre de la tabla)
_name = 'mi.modelo'
# Descripción que aparece en la interfaz
_description = 'Mi Modelo de Ejemplo'
# Campos del modelo
name = fields.Char('Nombre', required=True)
active = fields.Boolean('Activo', default=True)
Tipos de campos básicos
| Tipo | Descripción | Ejemplo |
|---|---|---|
Char |
Texto corto | fields.Char('Nombre', required=True) |
Text |
Texto largo | fields.Text('Descripción') |
Integer |
Número entero | fields.Integer('Cantidad') |
Float |
Número decimal | fields.Float('Precio') |
Boolean |
Verdadero/Falso | fields.Boolean('Activo', default=True) |
Date |
Fecha | fields.Date('Fecha') |
Datetime |
Fecha y hora | fields.Datetime('Fecha y Hora') |
Selection |
Lista desplegable | fields.Selection([('a','Opción A'), ('b','Opción B')]) |
Parámetros comunes de campos
| Parámetro | Descripción |
|---|---|
string |
Etiqueta del campo (primer parámetro) |
required=True |
Campo obligatorio |
default=valor |
Valor por defecto |
readonly=True |
Solo lectura |
help='texto' |
Tooltip de ayuda |
🔗 Campos Relacionales
Many2one (Muchos a Uno)
Relación donde muchos registros de este modelo apuntan a un registro de otro modelo. Crea una clave foránea en la base de datos.
# En el modelo 'libro', muchos libros pueden tener el mismo autor
autor_id = fields.Many2one(
'res.partner', # Modelo relacionado
string='Autor', # Etiqueta
required=True, # Obligatorio
ondelete='cascade' # Qué hacer si se borra el autor
)
Opciones de ondelete:
'cascade'- Borra los registros relacionados'set null'- Pone el campo a NULL'restrict'- Impide borrar si hay registros relacionados
One2many (Uno a Muchos)
Relación inversa de Many2one. Un registro tiene muchos registros relacionados. NO crea campo en la base de datos, es virtual.
# En el modelo 'autor', un autor tiene muchos libros
libro_ids = fields.One2many(
'biblioteca.libro', # Modelo relacionado
'autor_id', # Campo Many2one en el otro modelo
string='Libros' # Etiqueta
)
⚠️ Importante: El segundo parámetro es el nombre del campo Many2one en el modelo relacionado.
Many2many (Muchos a Muchos)
Relación donde muchos registros pueden relacionarse con muchos registros. Crea una tabla intermedia.
# Un libro puede tener muchas categorías, una categoría muchos libros
categoria_ids = fields.Many2many(
'biblioteca.categoria', # Modelo relacionado
string='Categorías' # Etiqueta
)
# Forma extendida (opcional, para controlar nombres de tabla)
categoria_ids = fields.Many2many(
'biblioteca.categoria',
'libro_categoria_rel', # Nombre tabla intermedia
'libro_id', # Columna de este modelo
'categoria_id', # Columna del otro modelo
string='Categorías'
)
Resumen Rápido
| Tipo | Sintaxis | Ejemplo Real |
|---|---|---|
Many2one |
fields.Many2one('modelo', string='Label') |
Pedido → Cliente |
One2many |
fields.One2many('modelo', 'campo_m2o', string='Label') |
Cliente → Sus Pedidos |
Many2many |
fields.Many2many('modelo', string='Label') |
Producto ↔ Etiquetas |
⚙️ Métodos Computados
Campo Computado Básico
Un campo computado calcula su valor automáticamente basándose en otros campos. Usa el decorador @api.depends.
from odoo import models, fields, api
class Pedido(models.Model):
_name = 'mi.pedido'
cantidad = fields.Integer('Cantidad')
precio_unitario = fields.Float('Precio Unitario')
# Campo computado
total = fields.Float(
'Total',
compute='_compute_total' # Nombre del método
)
@api.depends('cantidad', 'precio_unitario')
def _compute_total(self):
for record in self:
record.total = record.cantidad * record.precio_unitario
Campo Computado con Inverse (Editable)
Para que un campo computado sea editable, añade el método inverse.
total = fields.Float(
'Total',
compute='_compute_total',
inverse='_inverse_total' # Permite editar
)
@api.depends('cantidad', 'precio_unitario')
def _compute_total(self):
for record in self:
record.total = record.cantidad * record.precio_unitario
def _inverse_total(self):
for record in self:
if record.cantidad:
record.precio_unitario = record.total / record.cantidad
Campo Computado Almacenado
Por defecto los campos computados NO se guardan en la base de datos. Usa store=True para guardarlos.
total = fields.Float(
'Total',
compute='_compute_total',
store=True # Se guarda en la BD
)
Estructura del @api.depends
| Situación | Sintaxis |
|---|---|
| Un campo | @api.depends('campo') |
| Varios campos | @api.depends('campo1', 'campo2') |
| Campo relacional | @api.depends('partner_id.name') |
| Campo One2many | @api.depends('line_ids.subtotal') |
🚫 Constraints (Restricciones)
Python Constraints (@api.constrains)
Valida datos usando lógica Python. Se ejecuta cuando se guardan los campos indicados.
from odoo import models, fields, api
from odoo.exceptions import ValidationError
class Producto(models.Model):
_name = 'mi.producto'
precio = fields.Float('Precio')
stock = fields.Integer('Stock')
@api.constrains('precio')
def _check_precio(self):
for record in self:
if record.precio < 0:
raise ValidationError('El precio no puede ser negativo')
@api.constrains('stock')
def _check_stock(self):
for record in self:
if record.stock < 0:
raise ValidationError('El stock no puede ser negativo')
SQL Constraints (_sql_constraints)
Restricciones a nivel de base de datos. Son más eficientes pero menos flexibles.
class Producto(models.Model):
_name = 'mi.producto'
codigo = fields.Char('Código')
precio = fields.Float('Precio')
# Lista de tuplas: (nombre, sql, mensaje_error)
_sql_constraints = [
('codigo_unique',
'UNIQUE(codigo)',
'El código debe ser único'),
('precio_positivo',
'CHECK(precio >= 0)',
'El precio debe ser positivo'),
]
Tipos comunes de SQL Constraints
| Tipo | SQL | Uso |
|---|---|---|
| Único | UNIQUE(campo) |
No repetir valores |
| Check | CHECK(campo > 0) |
Validar condición |
| Único compuesto | UNIQUE(campo1, campo2) |
Combinación única |
🖼️ Vistas XML
Vista Formulario (Form)
Muestra un registro individual para ver/editar.
<record id="view_producto_form" model="ir.ui.view">
<field name="name">producto.form</field>
<field name="model">mi.producto</field>
<field name="arch" type="xml">
<form string="Producto">
<sheet>
<group>
<group string="Información">
<field name="name"/>
<field name="codigo"/>
</group>
<group string="Precios">
<field name="precio"/>
<field name="stock"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
Vista Tree/Lista
Muestra varios registros en formato tabla.
<record id="view_producto_tree" model="ir.ui.view">
<field name="name">producto.tree</field>
<field name="model">mi.producto</field>
<field name="arch" type="xml">
<tree string="Productos">
<field name="name"/>
<field name="codigo"/>
<field name="precio"/>
<field name="stock"/>
</tree>
</field>
</record>
Vista Kanban
Muestra registros como tarjetas, ideal para flujos de trabajo.
<record id="view_tarea_kanban" model="ir.ui.view">
<field name="name">tarea.kanban</field>
<field name="model">mi.tarea</field>
<field name="arch" type="xml">
<kanban default_group_by="estado">
<field name="name"/>
<field name="estado"/>
<field name="responsable_id"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<strong><field name="name"/></strong>
<div>Responsable: <field name="responsable_id"/></div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
Vista Calendar
Muestra registros en un calendario basándose en campos de fecha.
<record id="view_evento_calendar" model="ir.ui.view">
<field name="name">evento.calendar</field>
<field name="model">mi.evento</field>
<field name="arch" type="xml">
<calendar string="Eventos"
date_start="fecha_inicio"
date_stop="fecha_fin"
color="tipo_id">
<field name="name"/>
<field name="responsable_id"/>
</calendar>
</field>
</record>
Action (Acción de ventana)
Conecta el menú con las vistas.
<record id="action_producto" model="ir.actions.act_window">
<field name="name">Productos</field>
<field name="res_model">mi.producto</field>
<field name="view_mode">tree,form,kanban</field>
</record>
🔒 Seguridad
Archivo ir.model.access.csv
Define los permisos CRUD (Create, Read, Update, Delete) por grupo de usuarios.
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_producto_user,producto.user,model_mi_producto,base.group_user,1,1,1,0
access_producto_admin,producto.admin,model_mi_producto,base.group_system,1,1,1,1
Estructura del CSV
| Columna | Descripción | Ejemplo |
|---|---|---|
id |
Identificador único | access_mi_modelo_user |
name |
Nombre descriptivo | mi.modelo.user |
model_id:id |
Modelo (model_nombre_punto_a_guion_bajo) | model_mi_modelo |
group_id:id |
Grupo de usuarios | base.group_user |
perm_read |
Permiso lectura (1/0) | 1 |
perm_write |
Permiso escritura (1/0) | 1 |
perm_create |
Permiso crear (1/0) | 1 |
perm_unlink |
Permiso borrar (1/0) | 0 |
Grupos comunes de Odoo
base.group_user- Usuarios internosbase.group_system- Administradoresbase.group_public- Usuarios públicos
⚠️ Importante
El nombre del modelo en model_id:id se forma así:
mi.producto→model_mi_productobiblioteca.libro→model_biblioteca_libro
📦 Archivo __manifest__.py
Estructura básica
El manifest define la metadata del módulo: nombre, dependencias, archivos a cargar, etc.
{
'name': 'Mi Módulo',
'version': '1.0',
'summary': 'Descripción corta del módulo',
'description': """
Descripción larga del módulo.
Puede tener varias líneas.
""",
'author': 'Tu Nombre',
'category': 'Uncategorized',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/producto_views.xml',
'views/menus.xml',
],
'installable': True,
'application': True,
'auto_install': False,
}
Campos importantes
| Campo | Descripción | Obligatorio |
|---|---|---|
name |
Nombre visible del módulo | ✅ Sí |
version |
Versión del módulo | ✅ Sí |
depends |
Lista de módulos de los que depende | ✅ Sí |
data |
Lista de archivos XML/CSV a cargar | ❌ No |
installable |
Si el módulo se puede instalar | ❌ No (default True) |
application |
Si aparece como aplicación principal | ❌ No |
Orden de los archivos en data
⚠️ El orden importa: Los archivos se cargan en el orden indicado. Primero seguridad, luego vistas, luego menús.
'data': [
'security/ir.model.access.csv', # 1º Seguridad
'views/producto_views.xml', # 2º Vistas
'views/menus.xml', # 3º Menús y acciones
'data/datos_demo.xml', # 4º Datos iniciales
]