odoo

Odoo ORM Explained: Complete Guide to Object-Relational Mapping

In this articles we will learn about the odoo ORM and usecase as well. how Odoo ORM  can be used in optimal way and other related stuff as well.

Key Odoo ORM Methods & Hook

1.default_get(self, fields_list)

  • Purpose: Provide dynamic default values when creating records.
  • When it’s called: Automatically when creating a new record (via UI or create())
  • Real-World Use Cases:
    • Prefill current user: user_id = self.env.user
    • Prefill related record from context: project_id = context['default_project_id']
    • Set default values based on other models: auto-assign employee based on logged-in user
  • Always call super()

2.create(self, vals)

  • Purpose: Intercept record creation, modify values, or add custom logic.
  • When it’s called: When creating records with create() or through UI
  • Real-World Use Cases:
    • Auto-generate sequence number for a record
    • Validate some fields before creation
    • Automatically link related records

Example:

@api.model
def create(self, vals):
    if 'name' not in vals:
        vals['name'] = self.env['ir.sequence'].next_by_code('my.task') or 'New'
    record = super().create(vals)
    record.log_creation()  # Custom method
    return record

Key Note:
Always return super().create(vals) object.

 

3.write(self, vals)

  • Purpose: Intercept updates to records, enforce validations, trigger side-effects.
  • When it’s called: On record updates (record.write({...}))
  • Real-World Use Cases:
    • Prevent a field from being changed under certain conditions
    • Auto-update related models when a field changes
    • Add audit logs for critical updates
def write(self, vals):
    if 'state' in vals and vals['state'] == 'done':
        vals['done_date'] = fields.Date.today()
    return super().write(vals)

4.unlink(self)

  • Purpose: Intercept deletion of records
  • When it’s called: On record.unlink()
  • Real-World Use Cases:
    • Prevent deletion of records under certain states (like confirmed invoices)
    • Clean up related records
    • Raise UserError to prevent deletion
def unlink(self):
    for rec in self:
        if rec.state == 'done':
            raise UserError("Cannot delete completed tasks")
    return super().unlink()

5.copy(self, default=None)

  • Purpose: Override how records are duplicated
  • When it’s called: On “Duplicate” button
  • Real-World Use Cases:
    • Remove or reset unique fields when duplicating (like internal reference)
    • Auto-increment a number for the new record
def copy(self, default=None):
    default = dict(default or {})
    default['code'] = self.env['ir.sequence'].next_by_code('my.code') or '/'
    return super().copy(default)

6.name_get(self)

  • Purpose: Control how records are displayed in many2one or dropdowns
  • When it’s called: Whenever the system needs a display name
  • Real-World Use Cases:
    • Show concatenated fields: First Last
    • Add extra info: [ID] Name
    • Include status in display name: Task (Done)
def name_get(self):
    result = []
    for rec in self:
        name = f"{rec.code} - {rec.name}" if rec.code else rec.name
        result.append((rec.id, name))
    return result

7.name_search(self, name='', args=None, operator='ilike', limit=100)

  • Purpose: Customize search behavior for many2one fields
  • When it’s called: When typing in a dropdown/many2one search
  • Real-World Use Cases:
    • Allow searching by code or name
    • Filter out inactive records in search
def name_search(self, name='', args=None, operator='ilike', limit=100):
    args = args or []
    domain = ['|', ('name', operator, name), ('code', operator, name)] + args
    records = self.search(domain, limit=limit)
    return records.name_get()

8.fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False)

  • Purpose: Customize the view dynamically before rendering
  • When it’s called: When loading form/tree/kanban view
  • Real-World Use Cases:
    • Hide fields based on user group
    • Change labels or help text dynamically
    • Add default filters dynamically

9.onchange Decorator (@api.onchange)

  • Purpose: Compute values before saving, triggered on field change in UI
  • When it’s called: Only in form view, client-side
  • Real-World Use Cases:
    • Auto-fill fields when another field is selected
    • Update totals dynamically
    • Show warning messages in UI
@api.onchange('product_id')
def _onchange_product(self):
    if self.product_id:
        self.price_unit = self.product_id.list_price

10.@api.constrains

  • Purpose: Validate fields after creation/update
  • When it’s called: On create() or write() automatically
  • Real-World Use Cases:
    • Prevent end date < start date
    • Salary must be > 0
    • Unique constraint across multiple fields
@api.constrains('start_date', 'end_date')
def _check_dates(self):
    for rec in self:
        if rec.end_date < rec.start_date:
            raise ValidationError("End date cannot be before start date")


About author

author image

Amrit panta

Fullstack developer, content creator



Scroll to Top