📚 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:

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

⚠️ Importante

El nombre del modelo en model_id:id se forma así:

📦 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
]