Odoo · Desarrollo · Convenciones OCA
Aprende a crear un módulo Odoo 19 siguiendo las reglas que define la comunidad: estructura de carpetas, manifest, seguridad, ORM y buenas prácticas.
Fundamentos
¿Qué es un módulo Odoo?
Un módulo (addon) es la unidad de extensión de Odoo. Puede añadir modelos, vistas, menús, datos, informes, assets web y lógica de negocio. La comunidad OCA (Odoo Community Association) y la documentación oficial establecen convenciones que garantizan calidad y compatibilidad entre proyectos.
«Un módulo hace una cosa y la hace bien. Extiende sin modificar el núcleo.»
Estructura
Árbol de directorios recomendado
mi_modulo/
├── __manifest__.py # Metadatos y dependencias
├── __init__.py # Importa models/, controllers/, wizards/
├── models/
│ ├── __init__.py
│ └── mi_modelo.py
├── views/
│ └── mi_modelo_views.xml
├── security/
│ ├── ir.model.access.csv # Permisos ACL
│ └── security.xml # Grupos y record rules
├── data/ # Datos XML/CSV (cron, secuencias…)
├── wizards/ # Asistentes transitorios
├── reports/ # Informes QWeb
├── static/src/ # JS, SCSS, XML (OWL)
├── i18n/ # Traducciones (.po)
└── tests/ # Tests unitarios
Manifest
El fichero __manifest__.py
Es el «DNI» del módulo. Define nombre, versión, dependencias, ficheros de datos y metadatos:
# -*- coding: utf-8 -*-
{{
'name': 'Mi Módulo',
'version': '19.0.1.0.0', # Mayor.Odoo.Menor.Revisión.Patch
'category': 'Sales',
'summary': 'Descripción corta en una línea',
'author': 'Tu Nombre',
'license': 'LGPL-3',
'depends': ['base', 'sale'],
'data': [
'security/ir.model.access.csv',
'views/mi_modelo_views.xml',
],
'installable': True,
'application': False,
}}
Reglas del manifest
versionempieza por la versión de Odoo (19.0)dependslista solo módulos estrictamente necesariosdata: seguridad primero, menús al finaldemoseparado dedata- Declara
external_dependenciessi usas librerías Python externas
Modelos
Definir modelos con el ORM
Cada modelo Python mapea a una tabla PostgreSQL. Usa _name en formato prefijo.modelo:
from odoo import api, fields, models
class MiModelo(models.Model):
_name = 'mi.modulo'
_description = 'Mi Modelo'
name = fields.Char(string='Nombre', required=True)
partner_id = fields.Many2one('res.partner', string='Cliente')
state = fields.Selection([
('draft', 'Borrador'),
('done', 'Hecho'),
], default='draft')
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
return records
| Regla OCA / Odoo | Por qué |
|---|---|
Siempre super() en create/write/unlink | Respeta herencias y triggers |
@api.model_create_multi en create | Rendimiento en lotes (Odoo 19) |
@api.depends explícito en computes | Evita recálculos innecesarios |
_inherit para extender, no modificar core | Principio Open/Closed (SOLID) |
Strings en inglés en código; traducciones en i18n/ | Estándar internacionalización |
Seguridad
ACL y reglas de acceso
Fichero security/ir.model.access.csv:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mi_modulo_user,mi.modulo.user,model_mi_modulo,base.group_user,1,1,1,0
access_mi_modulo_manager,mi.modulo.manager,model_mi_modulo,base.group_system,1,1,1,1
- Restrictivo por defecto: solo los permisos necesarios
- Record rules en
security.xmlpara filtrar por usuario/equipo - Nunca
sudo()en controladores web ni APIs públicas - Campos sensibles con
groups=restrictivo en la definición del campo
Vistas
XML con convenciones de nomenclatura
<!-- views/mi_modelo_views.xml -->
<record id="view_mi_modulo_form" model="ir.ui.view">
<field name="name">mi.modulo.form</field>
<field name="model">mi.modulo</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
<field name="partner_id"/>
<field name="state"/>
</group>
</sheet>
</form>
</field>
</record>
Convenciones de IDs XML: view_<modelo>_<tipo>, action_<modelo>, menu_<nombre>. En Odoo 17+ usa t-out en lugar de t-esc en plantillas QWeb.
Calidad
Testing y herramientas
| Herramienta | Uso |
|---|---|
TransactionCase | Tests transaccionales con rollback |
SavepointCase | Tests rápidos con savepoints |
black + isort | Formateo automático Python |
flake8 + pylint-odoo | Linting y reglas OCA |
pre-commit | Hooks antes de cada commit |
# tests/test_mi_modulo.py
from odoo.tests import TransactionCase
class TestMiModulo(TransactionCase):
def test_create_record(self):
record = self.env['mi.modulo'].create({{'name': 'Test'}})
self.assertEqual(record.state, 'draft')
Instalación
Activar tu módulo
- Coloca el módulo en
odoo/addons/custom/mi_modulo/ - Reinicia Odoo:
docker compose restart odoo - Actualiza lista de apps: Ajustes → Activar modo desarrollador → Actualizar lista
- Busca «Mi Módulo» en Aplicaciones e instala
- O desde terminal:
make update MODULE=mi_modulo
Referencias
Documentación y comunidad
- Documentación oficial Odoo 19 — Developer
- OCA Maintainer Tools — guías de contribución
- Odoo Community Association
Resumen
Un módulo Odoo 19 bien hecho respeta la estructura de carpetas, declara dependencias mínimas, protege cada modelo con ACL, usa el ORM correctamente y incluye tests. Seguir las convenciones OCA no es burocracia: es la diferencia entre un prototipo y software mantenible.