If you're building or maintaining Odoo modules and need to follow OCA conventions, this handles the grunt work. It scaffolds new modules with proper structure, validates against OCA standards (naming, manifest keys, file organization), and guides you through OpenUpgrade migrations between versions 14.0 to 19.0. The migration support is the standout bit here, with pre and post migration patterns for field renames, model changes, and value mapping. It includes scripts for both module generation and validation, plus enforces the little things like singular naming and proper author attribution. Saves you from memorizing OCA's specific folder structure and manifest format every time you start a module.
npx -y skills add miquelalzanillas/odoo-oca-convention-skill --skill odoo-oca-developer --agent claude-codeInstalls into .claude/skills of the current project.
Expert assistant for Odoo module development following OCA conventions and best practices.
Create new Odoo modules from OCA template with proper structure and conventions.
Quick Start:
python scripts/init_oca_module.py my_module_name --path /path/to/addons --version 17.0
What this provides:
__manifest__.py with required keys__init__.py importsModule naming conventions:
sale_order_import (not sale_orders_import)base_ (e.g., base_location_nuts)l10n_CC_ (e.g., l10n_es_pos)mail_forward)crm_partner_firstname)Follow OCA conventions strictly. Reference oca_conventions.md for detailed guidelines.
Essential structure:
module_name/
├── __init__.py
├── __manifest__.py
├── models/
│ ├── __init__.py
│ └── <model_name>.py
├── views/
│ └── <model_name>_views.xml
├── security/
│ ├── ir.model.access.csv
│ └── <model_name>_security.xml
├── data/
│ └── <model_name>_data.xml
├── readme/
│ ├── DESCRIPTION.rst
│ ├── USAGE.rst
│ └── CONTRIBUTORS.rst
└── tests/
├── __init__.py
└── test_<feature>.py
Key principles:
models/sale_order.pyviews/sale_order_views.xml_demo suffix: demo/sale_order_demo.xmlmigrations/17.0.1.0.0/manifest.py essentials:
{
'name': 'Module Name',
'version': '17.0.1.0.0', # {odoo}.x.y.z format
'category': 'Sales',
'license': 'AGPL-3', # or LGPL-3
'author': 'Your Company, Odoo Community Association (OCA)',
'website': 'https://github.com/OCA/<repository>',
'depends': ['base', 'sale'],
'data': [
'security/ir.model.access.csv',
'views/model_name_views.xml',
],
'installable': True,
}
Python code structure:
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class SaleOrder(models.Model):
_inherit = 'sale.order'
# Fields
custom_field = fields.Char(string="Custom Field")
# Compute methods
@api.depends('order_line')
def _compute_total(self):
for order in self:
order.total = sum(order.order_line.mapped('price_total'))
# Business methods
def action_custom(self):
self.ensure_one()
# Implementation
XML naming conventions:
<model_name>_view_<type> (e.g., sale_order_view_form)<model_name>_action (e.g., sale_order_action)<model_name>_menu<model_name>_group_<name>_demoMigrate modules between Odoo versions following OpenUpgrade patterns. See openupgrade_migration.md for complete guide.
Migration structure:
module_name/
└── migrations/
└── 17.0.1.0.0/
├── pre-migration.py
├── post-migration.py
└── noupdate_changes.xml
Pre-migration example:
from openupgradelib import openupgrade
@openupgrade.migrate()
def migrate(env, version):
# Rename fields before module loads
openupgrade.rename_fields(env, [
('sale.order', 'sale_order', 'old_field', 'new_field'),
])
# Rename models
openupgrade.rename_models(env.cr, [
('old.model', 'new.model'),
])
Post-migration example:
from openupgradelib import openupgrade
@openupgrade.migrate()
def migrate(env, version):
# Map old values to new
openupgrade.map_values(
env.cr,
openupgrade.get_legacy_name('state'),
'state',
[('draft', 'pending'), ('confirm', 'confirmed')],
table='sale_order',
)
# Recompute fields
env['sale.order'].search([])._compute_total()
Common migration tasks:
openupgrade.rename_fields()openupgrade.rename_models()openupgrade.rename_tables()openupgrade.map_values()openupgrade.delete_records_safely_by_xml_id()Extend core Odoo modules following OCA patterns.
Inherit existing model:
from odoo import fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
custom_field = fields.Char(string="Custom Info")
Extend existing view:
<record id="res_partner_view_form" model="ir.ui.view">
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='email']" position="after">
<field name="custom_field"/>
</xpath>
</field>
</record>
Module dependencies:
__manifest__.pydepends key for Odoo core/OCA modulesexternal_dependencies for Python packagesValidate module structure:
python scripts/validate_module.py /path/to/module
What is checked:
__init__.py, __manifest__.py)Code quality tools:
# Install pre-commit for OCA checks
pip install pre-commit
pre-commit install
# Run checks
pre-commit run --all-files
# Run specific checks
flake8 module_name/
pylint --load-plugins=pylint_odoo module_name/
"I need to create a new Odoo module"
→ Use scripts/init_oca_module.py to generate OCA-compliant structure
→ Edit __manifest__.py with module details
→ Create models in models/ directory
→ Create views in views/ directory
→ Add security rules in security/
→ Update readme/ documentation
→ Run validation: scripts/validate_module.py
"I need to migrate a module to a new Odoo version"
→ Check OpenUpgrade for breaking changes
→ Create migration folder: migrations/<new_version>/
→ Write pre-migration script for schema changes
→ Write post-migration script for data transformation
→ Test on copy of production database
→ Reference openupgrade_migration.md
"I need to extend a core Odoo module"
→ Create new module with core module in depends
→ Use _inherit to extend models
→ Use inherit_id to extend views
→ Follow OCA naming: <core_module>_<feature>
→ Keep changes minimal and focused
"I'm not sure if my module follows OCA conventions"
→ Run scripts/validate_module.py
→ Check oca_conventions.md
→ Review manifest.py for required keys
→ Verify file naming and structure
→ Ensure OCA author attribution
scripts/init_oca_module.py_logger.debug() for import errorsFormat: [TAG] module_name: short summary
Common tags:
[ADD] - New feature/module[FIX] - Bug fix[REF] - Refactoring[IMP] - Improvement[MIG] - Migration[REM] - Removaltotal = fields.Float(compute='_compute_total', store=True)
@api.depends('line_ids.amount')
def _compute_total(self):
for record in self:
record.total = sum(record.line_ids.mapped('amount'))
<xpath expr="//field[@name='partner_id']" position="after">
<field name="custom_field"/>
</xpath>
<record id="group_custom" model="res.groups">
<field name="name">Custom Access</field>
<field name="category_id" ref="base.module_category_sales"/>
</record>
openupgrade.map_values(
env.cr,
openupgrade.get_legacy_name('old_field'),
'new_field',
[('old_value', 'new_value')],
table='model_table',
)
Module not appearing in Apps
'installable': True in manifest.pyodoo-bin -u module_name -d databaseImport errors
Migration fails
\d table in psqlopenupgrade.logged_query() for debuggingTests failing
tagged('post_install', '-at_install')@users()freezegunCreate module:
python scripts/init_oca_module.py my_module --version 17.0
Validate module:
python scripts/validate_module.py path/to/module
Check conventions: See oca_conventions.md
Migration guide: See openupgrade_migration.md
Module template: Copy from assets/module_template/
cursor/plugins
metabase/metabase
metabase/metabase
telagod/code-abyss
github/awesome-copilot
DietrichGebert/ponytail