ODOO: Understanding Odoo Models

Odoo models are Python classes that map to database tables and encapsulate business logic through the ORM. This post covers the types of Odoo models, their fields, methods, and how the ORM eliminates the need to write raw SQL.

In Odoo, a Model is the backbone of every feature. It defines what data exists in the system, how it is structured, and how the business logic behaves. Every record you see in Odoo — a sales order, a customer, a product — is an instance of a model.

Understanding models is fundamental to Odoo development.


What is an Odoo Model?

A Model in Odoo represents a single business entity or concept such as a product, customer, order, or invoice. It is a Python class that defines the structure (fields) and behavior (methods) of the data.

Models correspond to database tables and encapsulate business logic. Odoo provides a powerful ORM (Object-Relational Mapping) system that allows developers to interact with the database using Python objects rather than writing raw SQL queries.

md
+--------------------------------------------------+
|              Odoo ORM Layer                      |
|                                                  |
|   Python Class  <-->  ORM  <-->  Database Table  |
|                                                  |
|   MyModel           SQL          my_model        |
|   .name             INSERT       name TEXT       |
|   .create()         UPDATE       ...             |
|   .search()         SELECT                       |
+--------------------------------------------------+


Types of Odoo Models

Odoo provides several model base classes, each suited for different purposes:

Model TypeBase ClassDatabase TablePurpose
Basic Modelmodels.ModelYesStandard business data
Transient Modelmodels.TransientModelYes (temporary)Wizards and dialogs
Abstract Modelmodels.AbstractModelNoShared logic/mixin

Basic Model (`models.Model`)

This is the standard model type used for most business logic and data storage. It creates a permanent database table.

python
from odoo import models, fields

class MyModel(models.Model):
    _name = 'my.model'
    _description = 'My Model'

    name = fields.Char(string='Name', required=True)
    description = fields.Text(string='Description')

Model Attributes

AttributeDescription
_nameTechnical name used as the database table name (dots become underscores)
_descriptionHuman-readable description of the model
_inheritInherit from another model
_orderDefault sort order for records
_rec_nameField used to represent the record as a string

Transient Model (`models.TransientModel`)

Transient models are used for temporary data, typically in wizards — dialogs that guide users through a multi-step process. Data in transient models is automatically deleted by a scheduled action after a configurable time period.

python
from odoo import models, fields

class MyTransientModel(models.TransientModel):
    _name = 'my.transient.model'
    _description = 'My Transient Model'

    name = fields.Char(string='Name', required=True)
    quantity = fields.Integer(string='Quantity', default=1)

Use cases:

  • Import/export wizards
  • Mass editing dialogs
  • Confirmation prompts
  • Report parameter forms

Abstract Model (`models.AbstractModel`)

Abstract models are used as base classes (mixins) that other models can inherit from. They do not create database tables. They exist solely to share common fields and methods across multiple models.

python
from odoo import models, fields

class MyAbstractModel(models.AbstractModel):
    _name = 'my.abstract.model'
    _description = 'My Abstract Model'

    name = fields.Char(string='Name')
    active = fields.Boolean(string='Active', default=True)

Any model that inherits from this abstract model will gain the name and active fields automatically.


Fields: Defining Data Structure

Fields define the columns in the database table and the data each record can hold. Odoo provides a rich set of field types.

Scalar Fields

Field TypeDescriptionExample
CharShort text stringName, reference
TextLong textDescription, notes
IntegerWhole numberQuantity, count
FloatDecimal numberPrice, weight
BooleanTrue/False toggleIs active, is published
DateDate onlyBirth date, due date
DatetimeDate and timeCreated at, scheduled time
SelectionDropdown listState, priority
HtmlRich text / HTMLEmail body, website content
BinaryFile / binary dataAttachments, images

python
from odoo import models, fields

class Product(models.Model):
    _name = 'product.product'
    _description = 'Product'

    name = fields.Char(string='Product Name', required=True)
    description = fields.Text(string='Description')
    price = fields.Float(string='Price', digits=(10, 2))
    qty_on_hand = fields.Integer(string='Quantity on Hand')
    is_active = fields.Boolean(string='Active', default=True)
    date_available = fields.Date(string='Available From')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('available', 'Available'),
        ('discontinued', 'Discontinued'),
    ], string='State', default='draft')


Relational Fields

Relational fields define relationships between models, which correspond to foreign keys and join tables in the database.

Many2one

A many-to-one relationship. Many records of this model belong to one record of the related model.

python
partner_id = fields.Many2one(
    'res.partner',
    string='Customer',
    required=True,
    ondelete='restrict'
)

md
sale.order  -->  res.partner
(many)           (one)

One2many

A one-to-many relationship. One record of this model has many related records in another model. This is the inverse of Many2one and requires a corresponding Many2one field in the related model.

python
order_line_ids = fields.One2many(
    'sale.order.line',
    'order_id',
    string='Order Lines'
)

md
sale.order  <--  sale.order.line
(one)            (many)

Many2many

A many-to-many relationship. Many records can be associated with many records on both sides. Odoo creates a join table automatically.

python
tag_ids = fields.Many2many(
    'product.tag',
    string='Tags'
)

md
product.product  <-->  product.tag
(many)                 (many)


Computed Fields

A computed field calculates its value dynamically from other fields rather than storing a fixed value in the database.

python
from odoo import models, fields, api

class SaleOrder(models.Model):
    _name = 'sale.order'

    order_line_ids = fields.One2many('sale.order.line', 'order_id')
    amount_total = fields.Float(
        string='Total Amount',
        compute='_compute_amount_total',
        store=True
    )

    @api.depends('order_line_ids.price_subtotal')
    def _compute_amount_total(self):
        for order in self:
            order.amount_total = sum(order.order_line_ids.mapped('price_subtotal'))

Key options:

OptionDescription
computeName of the method that calculates the value
store=TruePersist the computed value in the database (for searching/filtering)
@api.depends(...)Triggers recomputation when listed fields change

Model Methods

Models can define methods that implement business logic.

CRUD Methods

Odoo provides built-in CRUD methods that you can override:

python
@api.model
def create(self, vals):
    # Custom logic before creation
    vals['name'] = vals.get('name', '').upper()
    record = super().create(vals)
    # Custom logic after creation
    return record

def write(self, vals):
    result = super().write(vals)
    # Custom logic after update
    return result

def unlink(self):
    # Custom logic before deletion
    for record in self:
        if record.state == 'confirmed':
            raise UserError("Cannot delete a confirmed record.")
    return super().unlink()

Custom Business Methods

python
def action_confirm(self):
    for record in self:
        record.state = 'confirmed'
        record.date_confirmed = fields.Datetime.now()


ORM Query Methods

The Odoo ORM provides powerful methods for querying data:

python
# Search for records
partners = self.env['res.partner'].search([
    ('is_company', '=', True),
    ('country_id.name', '=', 'Egypt')
])

# Search with limit and order
top_customers = self.env['res.partner'].search(
    [('customer_rank', '>', 0)],
    limit=10,
    order='customer_rank desc'
)

# Search and count
count = self.env['sale.order'].search_count([('state', '=', 'sale')])

# Browse by ID
partner = self.env['res.partner'].browse(42)

# Read specific fields
data = self.env['res.partner'].search_read(
    [('is_company', '=', True)],
    fields=['name', 'email', 'phone']
)


The Environment (`self.env`)

Every model method has access to self.env, which provides:

PropertyDescription
self.env.userThe currently logged-in user
self.env.uidThe user ID
self.env.companyThe current company
self.env['model.name']Access any model by its technical name
self.env.crThe database cursor (for raw SQL when needed)

Final Thoughts

Odoo models are the heart of every Odoo application. They define what data exists, how it is stored, and how the business logic operates. The ORM abstracts away raw SQL while still providing powerful querying capabilities.

Mastering models — their types, fields, computed values, and ORM methods — gives you the foundation to build any business feature in Odoo without ever writing a single SQL query directly.

Every business process in Odoo begins with a well-designed model.