Uncommon ORM Techniques in Odoo 19. The majority of developers use fundamental functions like create(), write(), search(), and unlink() when creating Odoo modules. However, Odoo's ORM has a number of potent hidden features that can improve the usability and intelligence of your modules.
Let's explore six rare but useful ORM methods with simple, practical examples.
1. _compute_display_name() - Modify the Display of Records
What is it?
This technique manages how your records show up in search results, breadcrumbs, and dropdown fields (Many2one).
Use Case: Sales Order Display
class SaleOrder(models.Model):
_inherit = 'sale.order'
@api.depends('name', 'partner_id', 'amount_total', 'state')
def _compute_display_name(self):
for order in self:
name = order.name
if order.partner_id:
name += f" - {order.partner_id.name}"
if order.state == 'sale':
name += f" (${order.amount_total:,.2f})"
order.display_name = name
Shows: SO001 - ABC Company ($15,450.00)
2. name_search() - Smart Search in Dropdown Fields
What is it?
When you type in a Many2one dropdown field, this method determines what happens. It can search more than one field, but by default it only looks for the display name.
Why use it?
Permit users to search for records using various criteria
class Product(models.Model):
_name = 'product.product'
_inherit = 'product.product'
@api.model
def name_search(self, name='', domain=None, operator='ilike', limit=100):
if not name:
return super().name_search(name, domain, operator, limit)
else:
custom_domain = [
'|', '|', '|',
('name', operator, name), # Product name
('default_code', operator, name), # Internal reference
('barcode', operator, name), # Barcode
('categ_id.name', operator, name), # Category name
]
products = self.search(custom_domain + domain, limit=limit)
return [(product.id, product.display_name) for product in products]
3. default_get() - Set Smart Default Values
What is it?
When new records are created using this method, default values are provided. Defaults can be dynamically set according to user, date, context, or business logic.
Why use it?
Pre-filling fields intelligently will save users time.
class ProductTemplate(models.Model):
_inherit = 'product.template'
@api.model
def default_get(self, fields_list):
"""
Auto-fill description_sale with template text
"""
res = super(ProductTemplate, self).default_get(fields_list)
# Write into 'description_sale' field (Text field)
if 'description_sale' in fields_list:
res['description_sale'] = (
"Product features:\n"
"- Feature 1\n"
"- Feature 2\n"
"- Feature 3\n"
"\n"
"Please customize this description."
)
return res
4. copy() - Control Record Duplication
What is it?
When you duplicate a record, this method is called (Action > Duplicate). It determines what is copied and how.
Why use it?
Maintain data integrity, update relevant data, and make sure unique fields are cleared.
class SaleOrder(models.Model):
_inherit = 'sale.order'
def copy(self, default=None):
default = dict(default or {})
# Clear the confirmed state
default['state'] = 'draft'
# Update the name
default['name'] = 'Copy of ' + self.name
# Clear dates
default['commitment_date'] = False
default['date_order'] = fields.Datetime.now()
return super().copy(default)
5. fields_get() - Dynamic Field Properties
What is it?
Field definitions are returned by this method. It can be overridden to dynamically modify field properties (readonly, required, and invisible) according to context or user role.
Why use it?
Field requirements can be changed on the fly, sensitive data can be hidden, or fields can be made read-only for specific users.
class SaleOrder(models.Model):
_inherit = 'sale.order'
@api.model
def fields_get(self, allfields=None, attributes=None):
res = super().fields_get(allfields, attributes)
# Make discount field readonly for non-managers
if 'discount' in res:
if not self.env.user.has_group('sales_team.group_sale_manager'):
res['discount']['readonly'] = True
# Make margin visible only to managers
if 'margin' in res:
if not self.env.user.has_group('sales_team.group_sale_manager'):
del res['margin'] # Hide the field completely
return res
6. check_access_rule() - Custom Security Rules
What is it?
This approach goes beyond simple security rules to add personalized access control. Before any operation (read, write, create, delete), it is called.
Why use it?
Put in place intricate security logic, such as "prevent deletion of confirmed orders" or "users can only edit their own records".
from odoo import models
from odoo.exceptions import AccessError
class SaleOrder(models.Model):
_inherit = 'sale.order'
def check_access_rule(self, operation):
super().check_access_rule(operation)
# Prevent deletion of confirmed orders
if operation == 'unlink':
for order in self:
if order.state in ['sale', 'done']:
raise AccessError(
f"Cannot delete confirmed order {order.name}. "
"Please cancel it first."
)
# Only managers can write to confirmed orders
if operation == 'write':
for order in self:
if order.state == 'sale':
if not self.env.user.has_group(
'sales_team.group_sale_manager'):
raise AccessError(
f"Only sales managers can modify confirmed order {order.name}.")
Conclusion
These six ORM techniques are effective instruments that can:
- Enhance user experience with intelligent search and better display names
- Use intelligent defaults to save time.
- Use intelligent duplication to preserve data integrity.
- Use custom access rules to improve security.
- Use dynamic field properties to increase flexibility.
Start using them in your next Odoo project!
To read more about What are ORM Methods in Odoo 18, refer to our blog What are ORM Methods in Odoo 18.