Khichdi InfoTech
Skip to content

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.

_inherit extends table vs _name new table vs _inherits delegation.
  • Client / Channel

    Users & devices

  • Odoo Application

    Business logic

  • PostgreSQL

    System of record

  • Workers / Cron

    Async work

  • clientapp(HTTP)
  • appdb(ORM)
  • appjobs(queue)
When to use O2M line model vs M2M vs Reference field.
OptionBest whenTrade-off
ConfigureStandard Odoo covers the needLimited uniqueness
CustomizeDifferentiating workflowUpgrade cost
IntegrateExternal system of recordSync 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.

Schema review before a major build?

We design inheritance-friendly models aligned with how your teams actually sell, stock, and invoice.

Business technology and software delivery