Enable Dark Mode!
what-are-the-orm-methods-in-odoo-19.jpg
By: Hafsana CA

What Are the ORM Methods in Odoo 19

Technical Odoo 19 Odoo Enterprises Odoo Community

After working with Odoo for a while, you begin to see that everything eventually returns to the ORM. Whether you are developing a complex business process or a simple customization, you are always interacting with Odoo's data layer. More than just a technical abstraction, Odoo's Object Relational Mapping (ORM) serves as the framework for business logic, database operations, and code readability and maintainability. Understanding ORM techniques is a must, not an option, for developers.

Odoo 19's ORM continues to develop while upholding the principles that have molded Odoo over the years: data security, module consistency, and developer ease of use. The goal of this blog is to take a more deliberate approach to examining ORM techniques in Odoo 19—what they are, how they operate, and how to apply them correctly in practical development situations.

What Is ORM in Odoo?

The layer in Odoo called ORM (Object Relational Mapping) enables programmers to communicate with the database through Python objects rather than directly writing SQL queries. Every record in an Odoo model corresponds to a row in a database table. Developers can safely and systematically create, read, update, and delete records using ORM techniques.

The ORM is intricately woven into the Odoo framework. The ORM is already in use when you define models, fields, or business logic. In addition to being useful tools, methods like create(), search(), and write() are the norm for how Odoo expects developers to handle data while adhering to system limitations and business rules.

Why ORM Is Used in Odoo

Instead of writing raw SQL queries, Odoo uses ORM to communicate with the database in a structured and secure manner. It helps avoid data inconsistencies and security problems by automatically enforcing access rights, record rules, and business logic.

ORM makes the code simpler, more readable, and easier to maintain by using recordsets and fields rather than tables and joins. Above all, it guarantees uniformity among modules, enabling various components of the system to function seamlessly as the application expands.

Common ORM methods

Based on the kind of operation they carry out on records, Odoo ORM methods can be broadly categorized. Developers can select the best strategy and produce more effective code by categorizing methods rather than treating them individually.

Create / Update ORM Methods in Odoo

To guarantee that modifications are implemented securely and reliably, Odoo offers clearly defined ORM techniques for developing or updating operations.

The create() Method

To add new records to a model, use the create() method. It returns a recordset with the newly created record after receiving a dictionary of field values.

Basic Example:

partner = self.env['res.partner'].create({
    'name': 'John Doe',
    'email': 'john.doe@example.com',
})

Here, the ORM automatically:

  • Inserts the data into the database
  • Applies default values
  • Triggers constraints and computed fields
  • Respects access rights and record rules

One important advantage of create() is that it supports batch creation, which is both cleaner and more efficient than creating records inside a loop.

Batch create example:

partners = self.env['res.partner'].create([
    {'name': 'Alice'},
    {'name': 'Bob'},
])

Overriding the create() Method

In real projects, developers often need to add custom logic when a record is created. Odoo allows this by overriding the create() method.

@api.model
def create(self, vals):
    vals['reference'] = self.env['ir.sequence'].next_by_code('custom.sequence')
    return super().create(vals)

When overriding:

  • Always call super() to preserve core behavior
  • Avoid heavy logic inside create() to prevent performance issues
  • Keep the method focused and predictable
  • Custom.sequence is a unique code, which we also specify in the XML file.

The write() Method

The write() method is used to update existing records. Unlike create(), it operates on a recordset and updates all records in that set at once.

Example:

partners.write({
    'active': False
})

This single call can update multiple records efficiently. Internally, Odoo ensures that:

  • Field constraints are validated
  • Computed fields are recomputed
  • Business rules remain intact

Overriding the write() Method

Just like create(), the write() method can be overridden to add custom update logic.

def write(self, vals):
    res = super().write(vals)
    for record in self:
        record.log_update()
    return res

When overriding write():

  • Always call super() first or last, depending on the logic
  • Never assume self contains only one record
  • Avoid calling write() again inside write() to prevent recursion

Read / Search ORM Methods in Odoo

Just as crucial as producing or updating data is reading it. Depending on the use case, Odoo's ORM offers multiple ways to retrieve records, including full recordsets, raw field values, and optimized responses for operations that are sensitive to performance.

The search() Method

The search() method is used to retrieve records from a model based on a given domain. It returns a recordset that may contain one or multiple records, depending on the condition.

records = self.env['res.partner'].search(domain)

In addition to the domain, search() supports a few optional parameters that help control how records are fetched.

  • domain – Defines the filtering condition for records
  • offset – Skips a given number of records (commonly used for pagination)
  • limit – Restricts the maximum number of records returned
  • order – Specifies the sorting order of the result

Example:

partners = self.env['res.partner'].search(
    [('is_company', '=', True)],
    offset=0,
    limit=10,
    order='name asc'
)

When count=True, the method returns the total number of matching records:

company_count = self.env['res.partner'].search(
    [('is_company', '=', True)],
    count=True
)

The search() method always respects access rights and record rules, making it safe to use in business logic.

The browse() Method

The browse() method is used to create a recordset using known record IDs. Unlike search(), it does not apply any domain or filtering logic.

partner = self.env['res.partner'].browse(10)

One thing that is important to know when using browse() is that it does not call the database immediately. It is only when a field is retrieved that Odoo actually retrieves the data.

If the record ID passed does not exist, browse() does not throw an error immediately. Instead, the recordset is empty when it is accessed. This is why developers often check if the record exists before doing anything with it.

The browse() function is most commonly used when the record IDs are already known, such as when dealing with button clicks, fields, or references passed from other models.

The read() Method

The read() method is used to fetch field values from records in a structured format. Instead of returning a recordset, it returns a list of dictionaries containing the requested fields.

data = self.env['res.partner'].browse(10).read(['name', 'email'])

The result will be a list where each item represents a record and its field values. This method is commonly used when data needs to be sent to reports, APIs, or UI components where recordset behavior is not required.

Unlike accessing fields directly on a recordset, read() gives you raw values, which makes it useful for data serialization. 

The search_read() Method

The search_read() method is a convenience method that combines search() and read() into a single call. It is mainly used when you need filtered data in a simple, structured format without working with full recordsets.

data = self.env['res.partner'].search_read(
    domain=[('is_company', '=', True)],
    fields=['name', 'email']
)

This method returns a list of dictionaries, similar to read(), but applies the domain filter internally. Because it reduces the number of ORM calls, search_read() is often used in controllers, dashboards, and API responses where performance matters.

While search_read() is efficient, it is generally not recommended for complex business logic. In such cases, using search() with recordsets gives more flexibility and control.

The search_count() Method

The search_count() method is used to get the number of records that match a given domain. Unlike search(), it does not return a recordset—only an integer value.

count = self.env['res.partner'].search_count([
    ('is_company', '=', True)
])

This method is commonly used in validations, statistics, and dashboard logic where only the total number of records matters. It is more efficient than using search() and then calculating the length of the recordset.

Since search_count() also respects access rights and record rules, it is safe to use in business logic.

The name_search() Method

The name_search() method is mainly used for autocomplete and dropdown fields in the UI. It searches records based on a text input and returns a list of (id, display_name) tuples.

results = self.env['res.partner'].name_search('John')

This method is commonly triggered when users type into a Many2one field. Developers usually override name_search() to customize how records appear in search dropdowns.

The search_fetch() Method

The search_fetch() method is used to search records and fetch only specific fields efficiently. It is designed to reduce memory usage by avoiding full recordset creation when only limited data is needed.

records = self.env['res.partner'].search_fetch(
    domain=[('is_company', '=', True)],
    field_names=['name', 'email']
)

This method is useful in performance-sensitive scenarios such as APIs or background jobs where loading complete records is unnecessary.

The read_group() Method

The read_group() method is used for grouped and aggregated data, similar to SQL GROUP BY. It is widely used in reporting and analytical views.

data = self.env['sale.order'].read_group(
    domain=[],
    fields=['amount_total'],
    groupby=['state']
)

This method returns aggregated values like sums and counts based on the specified group. It is the backbone of Odoo’s reporting features and dashboard metrics.

Delete ORM Method in Odoo

The unlink() Method

The unlink() method is used to delete records from the database. It permanently removes one or multiple records represented by the recordset.

partners.unlink()

The method works on a recordset, which means a single call can delete multiple records at once. Before deletion, Odoo automatically checks access rights and record rules, ensuring that only authorized users can remove data.

Overriding the unlink() Method

Just like create() and write(), the unlink() method can be overridden to add custom behavior before or after deletion.

def unlink(self):
    for record in self:
        record.log_deletion()
    return super().unlink()

When overriding unlink():

  • Always call super() to ensure proper cleanup
  • Never assume only one record is being deleted
  • Avoid heavy logic that could slow down mass deletion

Recordset Methods in Odoo

Recordset methods are one of the most elegant parts of Odoo’s ORM. They allow developers to work with records after they are fetched, without triggering extra database queries. These methods live at the Python level and are meant to make code expressive, safe, and readable.

The exists() Method

In real projects, data is rarely perfect. Records may be deleted, archived, or partially synced from external systems. This is where exists() quietly saves you.

partners = self.env['res.partner'].browse(ids).exists()

If some of the IDs no longer exist, exists() simply removes them from the recordset. No errors, no noise. This makes it ideal when:

  • Processing external data
  • Working with background jobs
  • Handling old references

Without exists(), you would need manual checks everywhere, which quickly clutters the code.

The ensure_one() Method

The self.ensure_one() method is used to make sure that a piece of logic is executed on exactly one record. When this method is called, Odoo checks the current recordset. If it contains zero records or more than one record, Odoo raises an error immediately.

self.ensure_one()

Without ensure_one(), the same code might behave unpredictably when accidentally executed on multiple records.

You’ll often see ensure_one() at the top of:

  • Button methods
  • Field computations
  • Actions triggered from form views

It turns hidden assumptions into explicit rules, which is exactly what good business logic should do.

The get_metadata() Method

The get_metadata() method is used to retrieve technical information about records, rather than business data. It returns metadata such as when a record was created, when it was last modified, and which users performed those actions.

metadata = self.get_metadata()

This method exposes technical information such as:

  • When the record was created
  • Who created or last modified it
  • When it was last updated

It’s useful when:

  • Building audit trails
  • Debugging unexpected changes
  • Creating administrative or compliance reports

Instead of manually reading system fields, get_metadata() keeps this information neatly packaged.

The with_context() Method

The with_context() method is used to temporarily modify how Odoo behaves while working with a recordset. It does not change the data itself; instead, it adjusts the execution environment for the operations that follow.

records = records.with_context(active_test=False)

Context values influence many parts of Odoo, including default filters, validations, computed fields, and business logic. For example, by setting active_test=False, inactive records are included in operations that would normally ignore them.

In practice, with_context() is used to pass flags that slightly alter behavior without complicating method signatures.

The with_company() Method

The with_company() method is used to execute operations under a specific company context.

records = records.with_company(company_id)

In multi-company setups, many fields, defaults, and rules depend on the active company. with_company() allows developers to explicitly control which company’s data and rules are applied during an operation.

This method is commonly used in:

  • Accounting logic
  • Automated processes spanning multiple companies
  • Background jobs that must respect company boundaries

By using with_company(), developers avoid subtle bugs caused by implicit company switching. It makes the intended company scope explicit, which is critical in financial and cross-company operations.

Operations on Recordsets

Once records are loaded into memory, Odoo provides recordset methods that help developers filter, transform, and organize data without making additional database queries. The filtered(), mapped(), and sorted() methods are central to this approach.

The filtered() Method

The filtered() method is used to filter an existing recordset using a Python condition. It returns a new recordset containing only the records for which the given condition evaluates to True.

Syntax:

recordset.filtered(function)

The function is usually a lambda expression that receives a single record and returns a Boolean value.

Example:

confirmed_orders = orders.filtered(lambda o: o.state == 'sale')

In this example, the orders recordset is filtered in memory, and only records with the state sale are kept.

The filtered() method operates at the Python level and does not trigger a new database query. This makes it useful when filtering depends on computed fields, transient values, or logic that cannot be expressed as a domain. However, because it works in memory, it should be used carefully with large recordsets. For large datasets, database-level filtering using search() is usually more efficient.

The mapped() Method

The mapped() method is used to extract values from a recordset. It returns a list of field values taken from each record in the recordset.

Syntax:

recordset.mapped(field_name)

You can also traverse relational fields using dot notation.

Basic Example

partners = self.env['res.partner'].search([])
emails = partners.mapped('email')

What happens here:

  • partners is a recordset
  • mapped('email') collects the email value from each partner
  • The result is a Python list of email addresses

Example

orders = self.env['sale.order'].search([])
product_names = orders.mapped('order_line.product_id.name')

Explanation:

  • orders contains multiple sale orders
  • Each order has multiple order_line records
  • Each order line points to a product_id
  • mapped() walks through all relationships, and collects product names

The result is a flat Python list of product names, without writing nested loops.

Without mapped(), the same logic would require multiple loops, making the code longer and harder to read. mapped() keeps the code expressive and aligns with Odoo’s recordset philosophy.

One thing to remember is that mapped() works in memory. It is best used when records are already loaded or when the dataset is reasonably sized.

The sorted() Method

The sorted() method is used to sort a recordset using Python logic. It returns a new recordset ordered based on the key provided.

Syntax:

recordset.sorted(key=None, reverse=False)

Basic Example

orders = self.env['sale.order'].search([])
sorted_orders = orders.sorted(key=lambda o: o.amount_total)

What happens here:

  • Sale orders are fetched as a recordset
  • sorted() orders them based on amount_total
  • The result is a new recordset sorted in ascending order

Example with Descending Order

sorted_orders = orders.sorted(
    key=lambda o: o.create_date,
    reverse=True
)

This sorts the records so the most recently created orders appear first.

The sorted() works in memory, it should be used carefully with large recordsets. If sorting can be handled directly in the database, using search(order=...) is usually more efficient.

The ORM is the backbone of how Odoo functions, and knowing how the methods work makes development easier and more secure. Whether it’s writing records, filtering, or sorting recordsets, each method has a specific use when used in the proper context. In Odoo 19, the ORM remains consistent in its patterns, which allows developers to spend less time on the database and more time on the business logic. Once these methods are ingrained, coding in Odoo will come much more naturally.

To read more about What are ORM Methods in Odoo 18, refer to our blog What are ORM Methods in Odoo 18.


Frequently Asked Questions

Is it safe to execute raw SQL instead of ORM calls in Odoo?

Raw SQL is supported in Odoo, but it is not advisable for standard business logic. Raw SQL ignores access rights, record rules, and business logic, which are automatically managed by ORM calls. With ORM, the system remains secure and stable, even during upgrades.

How does search() differ from filtered()?

search() performs filtering at the database level based on domains, while filtered() is done in Python after loading the records. search() is more efficient when handling a large number of records. filtered() is preferred when the filtering criteria involve dynamic values.

When should I use search_read() instead of search()?

search_read() is a good choice when you only need structured data and do not need recordset functionality. It is often used in controllers, APIs, and dashboards. For complex business logic, search() is a better choice.

Why does Odoo prefer working with recordsets over working with individual records?

Odoo is designed to work with recordsets by default. This makes it easier to avoid loops, increases performance, and ensures consistency across modules. Functions such as write() and unlink() are designed to work well with multiple records.

When should ensure_one() be used?

ensure_one() should be used when a method is expected to work on exactly one record, such as button actions or form-level logic. It helps catch mistakes early by raising an error if multiple or no records are passed.

If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, KINFRA Techno Park
Kakkanchery, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message