Loading...
Loading...
Expert guidance for developing, migrating, and maintaining Odoo modules (versions 14.0-19.0) following OCA conventions. Use when working with Odoo modules to create new modules from OCA template, migrate modules between versions using OpenUpgrade, extend core Odoo modules, ensure OCA compliance, and structure module files correctly. Includes OCA coding standards, module structure templates, OpenUpgrade migration patterns, and validation tools.
npx skill4agent add miquelalzanillas/odoo-oca-convention-skill odoo-oca-developerpython scripts/init_oca_module.py my_module_name --path /path/to/addons --version 17.0__manifest__.py__init__.pysale_order_importsale_orders_importbase_base_location_nutsl10n_CC_l10n_es_posmail_forwardcrm_partner_firstnamemodule_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>.pymodels/sale_order.pyviews/sale_order_views.xml_demodemo/sale_order_demo.xmlmigrations/17.0.1.0.0/{
'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,
}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<model_name>_view_<type>sale_order_view_form<model_name>_actionsale_order_action<model_name>_menu<model_name>_group_<name>_demomodule_name/
└── migrations/
└── 17.0.1.0.0/
├── pre-migration.py
├── post-migration.py
└── noupdate_changes.xmlfrom 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'),
])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()openupgrade.rename_fields()openupgrade.rename_models()openupgrade.rename_tables()openupgrade.map_values()openupgrade.delete_records_safely_by_xml_id()from odoo import fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
custom_field = fields.Char(string="Custom Info")<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>__manifest__.pydependsexternal_dependenciespython scripts/validate_module.py /path/to/module__init__.py__manifest__.py# 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/scripts/init_oca_module.py__manifest__.pymodels/views/security/readme/scripts/validate_module.pymigrations/<new_version>/depends_inheritinherit_id<core_module>_<feature>scripts/validate_module.pyscripts/init_oca_module.py_logger.debug()[TAG] module_name: short summary[ADD][FIX][REF][IMP][MIG][REM]total = 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',
)'installable': Trueodoo-bin -u module_name -d database\d tableopenupgrade.logged_query()tagged('post_install', '-at_install')@users()freezegunpython scripts/init_oca_module.py my_module --version 17.0python scripts/validate_module.py path/to/module