Odoo Development · intermediate · 13 min read
Models, Fields, and Relationships
Designing Odoo models: field types, Many2one/One2many/Many2many, inheritance modes, constraints, and schema decisions that survive reporting and upgrades.
Last reviewed 2026-07-30 · Estimated effort: 2–6 weeks typical implementation slice
Table of contents
Models are PostgreSQL tables (or views) exposed as Python classes. Fields declare columns, relations, and computed behavior. Choosing the right field type and relation cardinality affects UI, reporting, security, and migration cost.
_inherit extends existing models; _name creates new tables. Prefer extending sale.order or stock.picking when the process is still that document — parallel models force sync logic forever.
Relational fields need explicit ondelete on Many2one, inverse_name on One2many, and relation tables for Many2many. Constraints belong in SQL when possible, Python when rules need context or cross-field logic.
Bad schema decisions are expensive to unwind. Adding a Char field is cheap; splitting a parallel order document into sale.order six months later is not.
This article covers field catalog usage, inheritance modes in Odoo 17, and relationship patterns that keep modules readable and secure.
Scalars: Char (indexed sparingly), Text, Html (sanitize aware), Integer, Float, Monetary (always pair currency_id), Boolean, Date, Datetime, Selection, Binary. Use fields.Json for structured payloads when appropriate in newer versions.
Relational: Many2one (FK), One2many (inverse), Many2many (relation table). Reference and Many2oneReference exist for polymorphic patterns — use only when truly needed.
- ▸tracking=True on fields feeds chatter — do not track everything
- ▸copy=False prevents duplicate on copy=True models
- ▸index='btree' or 'trigram' for search-heavy Char fields
Classical extension: _inherit = 'sale.order' adds fields and methods to the same model/table. Delegation inheritance (_inherits) links child to parent record — rare in custom work, common in hr.employee patterns.
New models: _name = 'x.custom.request' with _description. Use mail.thread and mail.activity.mixin when chatter and activities are required — declare depends on mail.
One2many needs comodel_name and inverse_name matching the Many2one on the line model. Domain on One2many filters lines in UI — not a security boundary.
Many2many supports relation, column1, column2 for custom pivot tables when multiple M2M on same pair exist. For tagging, use standard M2M; for ordered lines with qty, use a separate line model.
_sql_constraints = [('name_uniq', 'unique(name)', 'Message')] enforces at DB level. @api.constrains validates after ORM writes — good for cross-field rules.
Override create/write when validation depends on related records or state transitions — call super() once after local checks.
Model _name uses dot notation (project.task.custom). Field names avoid reserved words and duplicate labels across inherited modules — use module prefix on generic names (x_kitech_priority).
External ids in XML data (module.model_field) stabilize upgrades; avoid renaming fields without migration scripts.
- ✓Extend core documents before inventing parallel headers/lines tables.
- ✓Set ondelete on every Many2one — cascade vs restrict is a business decision.
- ✓Use Monetary with company currency or document currency consistently.
- ✓Store computed fields used in domains, pivots, and record rules.
- ✓Keep line models for editable grids; M2M for tags and assignees.
- ✓Plan mail.thread only when users need audit trail — it adds write volume.
- !Char for money — use Monetary with proper rounding.
- !One2many without inverse Many2one on comodel.
- !Stored computed fields without proper @api.depends — stale data.
- !Renaming _name models after go-live without column migration.
- !Using related= chains five levels deep for stored reporting — flatten.
- !Constraints that raise generic Exception instead of ValidationError.
Models and fields are the contract between UX, security, and reporting. Invest design time before the first form view ships.
Wire access control next — ACL and record rules assume your schema and company fields exist.
Client / Channel
Users & devices
Odoo Application
Business logic
PostgreSQL
System of record
Workers / Cron
Async work
- clientapp(HTTP)
- appdb(ORM)
- appjobs(queue)
| Option | Best when | Trade-off |
|---|---|---|
| Configure | Standard Odoo covers the need | Limited uniqueness |
| Customize | Differentiating workflow | Upgrade cost |
| Integrate | External system of record | Sync complexity |
Frequently asked questions
AbstractModel vs Model vs TransientModel?
AbstractModel is a mixin — no table. Model is persistent. TransientModel is wizard storage with vacuum cron — not for business truth.
When is delegation inheritance appropriate?
When a record is a specialization of another table (partner-linked entity) and you want delegated fields. Most custom apps should use classical _inherit instead.
Can I add a required field to a model with existing rows?
Only with a default or a pre-migration script filling values before module -u sets required=True.
How do I order One2many lines?
Use a sequence Integer field defaulting to 10 and order='sequence,id' on the One2many field definition.
Related articles
How the Odoo ORM Works
Environments, recordsets, transactions, prefetch, and the create/write/unlink lifecycle — practical ORM behavior for Odoo 16/17 developers.
View →Security: Groups and Record Rules
Odoo access rights: ir.model.access.csv, security groups, record rules, field-level restrictions, and multi-company patterns that enforce server-side.
View →Writing Upgrade-Friendly Code
Engineering patterns that survive Odoo version upgrades: inheritance over fork, xpath stability, migration scripts, noupdate data, and avoiding private API coupling.
View →Testing Odoo Modules
Automated testing for Odoo addons: TransactionCase, SavepointCase, test tags, mocking RPC, view loading checks, and CI patterns for Odoo 16/17.
View →Schema review before a major build?
We design inheritance-friendly models aligned with how your teams actually sell, stock, and invoice.
