ORM, or Object-Relational Mapping, is a technology that allows developers to connect with databases using object-oriented programming rather than writing raw SQL queries.
In Odoo 18, ORM methods provide an interface between models and database, allowing for easy data manipulation and retrieval.
This method simplifies database management for developers, allowing them to focus on logic rather than SQL syntax.
Odoo’s use of ORM techniques simplifies data manipulation, allowing you to create, update, and delete records without having to write specific queries. This is why ORM is important for the building and modification of Odoo applications.
Example: Object-Relational Mapping (ORM) is at the heart of Odoo's framework, allowing Odoo developers to connect with the database in a more straightforward, efficient manner without having to write SQL queries.
Odoo18’s ORM has evolved, making it easier to manage complicated business logic using Python methods. Let’s look at the fundamental ORM methods in Odoo 18 and how they might improve development processes.
What is ORM in Odoo?
In Odoo, ORM (Object-Relational Mapping) is a core framework that allows developers to interact with the database using Python code instead of SQL queries.
The ORM in Odoo abstracts the complexities of SQL, enabling developers to create, read, update, and delete records in the database by interacting with Python objects directly.
This approach improves code readability, security, and maintainability, making it easier to manage complex business logic within Odoo’s system.
Key Features of Odoo’s ORM
Automatic Mapping
The ORM maps Odoo models (Python classes) to database tables, meaning each model corresponds to a table, and each field in the model corresponds to a column.
Seamless Database Operations
CRUD (Create, Read, Update, Delete) operations are simplified through ORM methods like create(), read(), write(), and unlink(). These methods manage complex database interactions without writing SQL code.
Relational Data Handling
Odoo’s ORM manages relationships like Many2one, One2many, and Many2many, allowing developers to link data across tables seamlessly.
Enhanced Security and Validation
The ORM adds security by preventing SQL injection attacks and automates data validation, making the database interactions safer.
Easy Customization
With ORM, developers can add new fields, methods, and validations to models and make these updates available across the application with minimal code.
Types of ORM Methods in Odoo 18
In Odoo 18, ORM methods are grouped into different types based on their functionality. These include CRUD methods, search methods, relational methods, and utility methods, each serving a distinct purpose in interacting with and managing the database.
1. Relational Methods
Many2one Relationships: The ORM manages many-to-one relationships, allowing to link one record from multiple records between two tables.
Example:
partner_id = fields.Many2one(“res.partner”)
- One2many and Many2many Relationships: One-to-many and many-to-many relationships link records to multiple related records. These methods allow complex data structures, like associating multiple products with a category.
product_ids = fields.Many2many(‘res.category’)
For one2many field like associating multiple order lines with one order and order_id is Many2one field as shown in the below example.
order_line = fields.One2many(‘sale.order.line’, ‘order_id’)
These ORM methods in Odoo 18 allow developers to efficiently manage data within the Odoo framework, enhancing productivity, maintainability, and security. Each method provides a unique way to interact with the database, providing various business logic implementations.
3. CRUD Operations (Create, Read, Update, Delete)
1. Create: Adding New Records
- Purpose: The create() method is used to insert a new record into the database. It takes a dictionary of field values and returns the newly created record.
new_customer = self.env['res.partner'].create({
'name': 'John Doe',
'email': 'john@example.com',
'country_id': 'US'
})
- Explanation: Here, a new record is created in the res.partner model (commonly representing customers or partners in Odoo), with specified name, email, and country ID.
2. Read: Retrieving Data
- Purpose: The read() and browse() methods are used to retrieve records. read() allows you to fetch specific fields for a record or a list of records, while browse() is more flexible and works like a cursor, providing access to record fields as attributes.
Example using read():
customer_data = self.env['res.partner'].read([1, 2], ['name', 'email'])
- Here, records with IDs 1 and 2 are read, returning only the name and email fields.
Example using browse():
customer = self.env['res.partner'].browse(1)
print(customer.name) # Accesses the name attribute of the customer record directly.
Explanation: read() is ideal when you want specific fields for multiple records at once, while browse() allows for on-demand access to fields and is useful for working with a single record or small datasets.
3. Update: Modifying Existing Records
- Purpose: The write() method updates existing records with new values. It takes a dictionary of field-value pairs to specify the changes.
Example:
customer.write({'email': 'newemail@example.com', 'name': 'Johnathan Doe'})
- Explanation: Here, the customer record’s email and name fields are updated. The write() method is useful for modifying fields without replacing the entire record.
4. Delete: Removing Records
- Purpose: The unlink() method is used to delete records from the database. Once deleted, the record cannot be recovered.
Example:
customer.unlink()
- Explanation: Here, the unlink() method deletes the customer record. It’s essential to use this method carefully, as deleting records can have cascading effects, especially in related records.
2. Search and Filtering Methods
1. Search: Finding Records Based on Criteria
- Purpose: The search() method retrieves records that match specific criteria. It returns a recordset of all records that fit the conditions, enabling both simple and complex filtering.
Syntax:
recordset = self.env['model.name'].search([('field_name', 'operator', 'value')])
Example:
us_customers = self.env['res.partner'].search([('country_id.code', '=', 'US')])
- Explanation: This example fetches all customer records where the country code is "US". Multiple criteria can be combined for complex filtering, e.g., search([('country_id.code', '=', 'US'), ('active', '=', True)]).
2. Search Count: Counting Records Matching Criteria
- Purpose: search_count() is used to get the number of records that meet a certain criteria, without fetching the actual records. This method is useful for reporting or analytics when only a count is needed.
Syntax:
count = self.env['model.name'].search_count([('field_name', 'operator', 'value')])
Example:
active_customers_count = self.env['res.partner'].search_count([('active', '=', True)])
- Explanation: This example returns the count of active customer records. Using search_count() instead of search() improves performance since only the count is fetched, not the full recordset.
3. Search Read: Combined Search and Read for Efficiency
- Purpose: The search_read() method combines the search() and read() methods to fetch specific fields of records that meet certain criteria. This method reduces the number of database calls, making it more efficient for retrieving specific data points.
Syntax:
result = self.env['model.name'].search_read([('field_name', 'operator', 'value')], ['field1', 'field2'])
Example:
customer_data = self.env['res.partner'].search_read([('country_id.code', '=', 'US')], ['name', 'email'])
- Explanation: This example retrieves only the name and email fields for customers in the US. search_read() is efficient as it fetches only necessary data in a single call, useful for displaying specific data on dashboards or lists.
4. Name Search: Searching Records by Display Name
- Purpose: The name_search() method is used to find records by their display name or a part of it. This is especially useful for autocomplete or dropdowns where users type in partial names.
Syntax:
result = self.env['model.name'].name_search('search_term')
Example:
search_results = self.env['res.partner'].name_search('John')
- Explanation: This searches for partners (e.g., customers) with "John" in their name and returns matching records. By default, name_search() matches display names but can be customized with additional criteria for more precise filtering.
3. Relationship Methods
1. Many2one: Creating a Single Record Link
- Definition: The Many2one relationship creates a link where multiple records in one model relate to a single record in another model (e.g., multiple orders linked to one customer).
- Usage:
- Define a Many2one field in a model, specifying the target model.
- This creates a dropdown or search field in the UI, letting users select a single related record.
Example:
class SaleOrder(models.Model):
_name = 'sale.order'
partner_id = fields.Many2one('res.partner', string='Customer')
- Explanation: Here, partner_id in sale.order references a customer in the res.partner model. Each sale order links to a single customer record.
- Methods:
- Set Value: Assign a related record by setting the Many2one field directly.
order.partner_id = customer # Setting a customer for the order - Access Related Fields: Use dot notation to access fields of the related record.
customer_name = order.partner_id.name # Accessing customer’s name
- Access Related Fields: Use dot notation to access fields of the related record.
2. One2many: Linking a Single Record to Multiple Records
- Definition: The One2many relationship represents a reverse link of Many2one, where a single record in one model is related to multiple records in another model (e.g., a customer has multiple orders).
- Usage:
- Define a One2many field, linking the target model with the field used for the relationship (often the reverse Many2one field).
Example:
class ResPartner(models.Model):
_name = 'res.partner'
order_ids = fields.One2many('sale.order', 'partner_id', string='Orders')
- Explanation: In this example, order_ids lists all orders associated with the partner, utilizing the reverse link with the partner_id field in sale.order.
- Methods: Add Records: Use the One2many field with a list of tuples for adding new related records.
partner.order_ids = [(0, 0, {'field_name': 'value'})] # Creates a new linked record
Modify Records: Use list operations for advanced operations like adding or removing specific related records.
partner.order_ids = [(4, order_id)] # Links an existing record
partner.order_ids = [(3, order_id)] # Unlinks the record
3. Many2many: Creating a Many-to-Many Link
- Definition: A Many2many relationship links multiple records in one model with multiple records in another model (e.g., products linked to multiple categories).
- Usage:
- Define a Many2many field with a target model, specifying a table that will store the links between the records.
Example:
class Product(models.Model):
_name = 'product.template'
category_ids = fields.Many2many('product.category', string='Categories')
- Explanation: In this case, a product can belong to multiple categories, and a category can have multiple products.
- Methods:
Add Records: Use tuples to add or link existing records.
product.category_ids = [(4, category_id)] # Link an existing category
product.category_ids = [(0, 0, {'name': 'New Category'})] # Create and link a new category
- Remove Records: Use operations to unlink records.
python
Copy code
product.category_ids = [(3, category_id)] # Unlink the category
4. Utility Methods
1. name_get() and name_search(): Handling Display and Search Names
- Purpose:
- name_get(): Defines how records are displayed as names, typically used in dropdowns or many-to-one fields.
- name_search(): Customizes search behavior for a model based on partial matches in display names.
Syntax:
def name_get(self):
result = []
for record in self:
name = f"{record.code} - {record.name}" # Customize name display
result.append((record.id, name))
return result
def name_search(self, name, args=None, operator='ilike', limit=100):
args = args or []
domain = [('name', operator, name)]
return self.search(domain + args, limit=limit).name_get()
- Usage:
- Use name_get() when a model requires customized names for user-friendly display.
- name_search() is useful for dynamic searching, such as finding records by partial names or including additional criteria.
2. default_get(): Setting Default Values Dynamically
- Purpose: The default_get() method fetches default values for fields in a model, allowing default data to be calculated based on conditions.
Syntax:
def default_get(self, fields):
res = super(MyModel, self).default_get(fields)
if 'field_name' in fields:
res['field_name'] = self.calculate_default_value()
return res
- Example:
- Automatically set default values based on the current user, date, or other contextual data.
- Usage: Use this method to populate default data when creating new records. For example, set a default country based on the user’s location.
3. onchange(): Triggering UI Updates Based on Field Changes
- Purpose: The onchange() decorator is used for methods that update fields in the UI when certain fields are modified.
Syntax:
@api.onchange('field_name')
def _onchange_field(self):
if self.field_name:
self.related_field = self.field_name * 2
- Example:
- Automatically adjust related fields based on user input without saving data to the database.
- Usage: Use onchange() for fields that need to react in real time to changes in the form, providing a more interactive experience without requiring a database write.
4. _compute() and _inverse(): Creating Computed and Inverse Fields
- Purpose:
- _compute(): Calculated field values based on other fields. These values are not stored in the database unless specified.
- _inverse(): Allows computed fields to be updated and written back to the database.
Syntax:
field_name = fields.Float(compute='_compute_field', inverse='_inverse_field')
@api.depend(‘other_field’)
def _compute_field(self):
for record in self:
record.field_name = record.other_field * 1.2 # Example computation
def _inverse_field(self):
for record in self:
record.other_field = record.field_name / 1.2
- Example:
- Automatically calculate a discount based on the product price and inverse it for bidirectional updates.
- Usage: Useful for fields requiring on-the-fly calculations or data derived from other fields.
5. validate(): Data Validation and Constraints
- Purpose: validate() or _check_* methods enforce data integrity by checking field values before saving records.
Syntax:
@api.constrains('field_name')
def _check_field_name(self):
for record in self:
if record.field_name < 0:
raise ValidationError("Field value cannot be negative.")
- Example:
- Validate that an inventory quantity is not negative or a date is within a valid range.
- Usage: Use validate() to ensure that data adheres to business rules and constraints.
6. copy(): Duplicating Records with Custom Logic
- Purpose: copy() duplicates a record, allowing modifications to certain fields during duplication.
Syntax:
def copy(self, default=None):
default = dict(default or {}, name=f"{self.name} (Copy)")
return super(MyModel, self).copy(default=default)
- Example:
- When duplicating a sales order, automatically append “(Copy)” to the name field to differentiate the duplicate.
- Usage: Use this method field to differentiate the duplicates needing custom adjustments, like adjusting references or default values.
Benefits of Using ORM Methods in Odoo 18
Increased Productivity: By reducing the need to write SQL, developers can focus on business logic.
Code Readability and Maintainability: Python-based database operations improve readability and make it easier to maintain and scale applications.
Database Independence: ORM abstracts the specifics of SQL, allowing Odoo to support multiple database systems.
In conclusion, Odoo’s ORM (Object-Relational Mapping) is a robust framework that simplifies database interactions while providing extensive functionality for managing complex operations, relational data, and business logic.
With its various types of methods—CRUD operations, search and filtering, relationship management, and utility functions—Odoo’s ORM enables developers to build efficient, scalable applications with minimal code and maximum functionality.
The ORM methods, from basic CRUD actions to specialized using and relationship management, offer flexibility and precision, catering to a wide range of business needs.
Whether it’s creating custom workflows, managing linked records, performing complex searches, or validating data, these methods ensure data integrity and provide a seamless user experience.