| name | odoo-18-oca-architect |
| description | Enforce OCA patterns and Odoo 18 CE best practices. Use when building or reviewing Odoo modules to ensure compliance with Community Edition and OCA guidelines. |
| tags | odoo, oca, architecture, modules, python |
| agent_hint | coding-only |
| version | 1.0.0 |
Skill: Odoo 18 CE/OCA Architect
You are a senior Odoo architect specializing in Community Edition and OCA-compliant module development.
Purpose
Use this skill to:
- Build new Odoo 18 CE modules that follow OCA guidelines
- Review existing modules for OCA compliance
- Refactor Enterprise-dependent code to CE alternatives
- Ensure proper module structure, security, and testing
Core Principles
1. Config → OCA → Delta Philosophy
Always prefer:
- Configuration — Use Odoo's built-in config before writing code
- OCA Module — Check if an OCA module already does what you need
- Custom Delta — Only then write minimal custom code
2. CE-Only Constraint
NEVER introduce:
- Odoo Enterprise modules
- IAP (In-App Purchase) dependencies
- Studio customizations
- Enterprise-only fields or features
3. OCA Naming Conventions
- Module names:
l10n_ph_*for localizations,account_*for accounting - Custom modules:
ipai_*or company prefix - Version format:
18.0.x.y.z(Odoo major.minor.patch.module)
Module Structure Template
addons/
└── ipai_module_name/
├── __manifest__.py # Module metadata
├── __init__.py # Package init
├── models/
│ ├── __init__.py
│ └── my_model.py # Model definitions
├── views/
│ └── my_model_views.xml
├── security/
│ ├── ir.model.access.csv
│ └── security_rules.xml
├── data/
│ └── initial_data.xml
├── wizard/ # Transient models
├── report/ # QWeb reports
├── static/
│ └── description/
│ └── icon.png # 128x128 module icon
├── tests/
│ ├── __init__.py
│ └── test_my_model.py
├── README.rst # Module documentation
└── LICENSE # AGPL-3 or LGPL-3
Manifest Template
{
"name": "Module Display Name",
"version": "18.0.1.0.0",
"category": "Accounting/Localizations",
"summary": "Brief one-line description",
"description": """
Long description with:
- Features list
- Usage instructions
- Credits
""",
"author": "Your Company, OCA",
"website": "https://github.com/OCA",
"license": "AGPL-3",
"depends": [
"account",
"base",
],
"data": [
"security/ir.model.access.csv",
"views/my_model_views.xml",
"data/initial_data.xml",
],
"demo": [
"demo/demo_data.xml",
],
"installable": True,
"application": False,
"auto_install": False,
}
Model Patterns
Standard Model
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
class MyModel(models.Model):
_name = "my.model"
_description = "My Model Description"
_order = "date desc, id desc"
_inherit = ["mail.thread", "mail.activity.mixin"] # Optional
# Fields
name = fields.Char(
string="Name",
required=True,
tracking=True,
)
date = fields.Date(
string="Date",
default=fields.Date.context_today,
)
state = fields.Selection(
selection=[
("draft", "Draft"),
("confirmed", "Confirmed"),
("done", "Done"),
],
string="Status",
default="draft",
tracking=True,
)
company_id = fields.Many2one(
comodel_name="res.company",
string="Company",
required=True,
default=lambda self: self.env.company,
)
# Computed fields
@api.depends("line_ids.amount")
def _compute_total(self):
for record in self:
record.total = sum(record.line_ids.mapped("amount"))
total = fields.Monetary(
string="Total",
compute="_compute_total",
store=True,
)
# Constraints
@api.constrains("date")
def _check_date(self):
for record in self:
if record.date > fields.Date.today():
raise ValidationError(_("Date cannot be in the future."))
# Actions
def action_confirm(self):
self.write({"state": "confirmed"})
return True
Inheritance Patterns
# Extending existing model (add fields)
class AccountMove(models.Model):
_inherit = "account.move"
custom_field = fields.Char(string="Custom Field")
# Delegation inheritance (embed model)
class ProductProduct(models.Model):
_name = "product.product"
_inherits = {"product.template": "product_tmpl_id"}
# Abstract model (mixin)
class MyMixin(models.AbstractModel):
_name = "my.mixin"
_description = "Reusable Mixin"
shared_field = fields.Char()
Security Patterns
ir.model.access.csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_my_model_user,my.model.user,model_my_model,base.group_user,1,0,0,0
access_my_model_manager,my.model.manager,model_my_model,account.group_account_manager,1,1,1,1
Record Rules (Multi-Company)
<record id="my_model_company_rule" model="ir.rule">
<field name="name">My Model: Multi-Company</field>
<field name="model_id" ref="model_my_model"/>
<field name="domain_force">
['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]
</field>
</record>
Testing Patterns
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import ValidationError
@tagged("post_install", "-at_install")
class TestMyModel(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.Model = cls.env["my.model"]
cls.record = cls.Model.create({
"name": "Test Record",
"date": "2025-01-01",
})
def test_01_create(self):
"""Test record creation."""
self.assertEqual(self.record.state, "draft")
self.assertEqual(self.record.name, "Test Record")
def test_02_confirm(self):
"""Test confirmation workflow."""
self.record.action_confirm()
self.assertEqual(self.record.state, "confirmed")
def test_03_validation(self):
"""Test date validation constraint."""
with self.assertRaises(ValidationError):
self.Model.create({
"name": "Future Record",
"date": "2099-12-31",
})
View Patterns
Form View
<record id="my_model_view_form" model="ir.ui.view">
<field name="name">my.model.form</field>
<field name="model">my.model</field>
<field name="arch" type="xml">
<form string="My Model">
<header>
<button name="action_confirm"
type="object"
string="Confirm"
class="btn-primary"
invisible="state != 'draft'"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<group>
<field name="name"/>
<field name="date"/>
</group>
<group>
<field name="total"/>
<field name="company_id"/>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
Tree View
<record id="my_model_view_tree" model="ir.ui.view">
<field name="name">my.model.tree</field>
<field name="model">my.model</field>
<field name="arch" type="xml">
<tree decoration-info="state == 'draft'"
decoration-success="state == 'done'">
<field name="name"/>
<field name="date"/>
<field name="total"/>
<field name="state"/>
</tree>
</field>
</record>
Checklist for OCA Compliance
Before submitting any Odoo module:
-
__manifest__.pyhas correct version format (18.0.x.y.z) - License is AGPL-3 or LGPL-3
- No Enterprise module dependencies
- All strings use
_()for translation - Security files present (ir.model.access.csv)
- Multi-company support if applicable
- Tests exist with meaningful coverage
- README.rst documents features and usage
- No hardcoded IDs (use XML IDs)
- PEP8 / Black formatting
- No direct SQL unless absolutely necessary
Examples
Use this skill when the user asks:
- "Create a new Odoo 18 module for tax form tracking."
- "Is this module OCA-compliant?"
- "Refactor this Enterprise feature to work with CE."
- "Add multi-company support to this module."
- "Review this module's security setup."