Skip to content

Learn Odoo Common ORM

Learn Odoo Common ORM

Method and Decorators Tutorial

Learn Odoo, Common ORM Method and Decorators enhance your development speed and code clarity from day one. In this tutorial, we guide you through those core concepts with clear examples, so you can apply them immediately. Moreover, we distribute the keyphrase evenly to maintain SEO focus and readability. Let’s dive into Odoo’s powerful ORM methods and decorators, and learn how they streamline your business logic.


Understand Odoo ORM Basics

First, we introduce the Odoo ORM (Object-Relational Mapping) system. Then, we explain how it simplifies database interactions. Moreover, we contrast it with raw SQL, so you can appreciate its benefits. Finally, we mention that we use familiar terminology to improve clarity.

Odoo ORM translates Python calls into SQL queries automatically. Therefore, you avoid writing raw SQL. Additionally, it ensures that your code remains database-agnostic. As a result, you can switch between PostgreSQL versions without rewriting queries.

For authoritative guidance, visit Odoo Official Documentation for more details.


Learn Common ORM Methods in Odoo

Next, we cover the common CRUD methods in Odoo ORM. Moreover, we include code examples and explain each step clearly.

Search Method in Odoo ORM

First, we use .search() to fetch records matching a domain filter. Moreover, you can combine multiple conditions with logical operators.

# Example: Find all partners in the US
partners = self.env['res.partner'].search([
    ('country_id.code', '=', 'US'),
    ('active', '=', True)
])
  • We call self.env['res.partner'] to access the partner model.
  • Then, we pass a list of tuples for the domain.
  • Finally, .search() returns a recordset of matching records.

Write Method for Updating Records

Next, we update existing records with .write(). Moreover, we show how to update one or many records at once.

# Example: Update phone number for a single partner
partner = self.env['res.partner'].browse(1)
partner.write({'phone': '08123456789'})
  • First, we obtain the record with .browse(id).
  • Then, we call .write() passing a dict of new field values.
  • Odoo applies the update in a single transaction.
# Example: Disable multiple inactive partners
old_partners = self.env['res.partner'].search([('last_login', '<', '2023-01-01')])
old_partners.write({'active': False})
  • We fetch all partners who haven’t logged in since a date.
  • Then, we set active=False for all records in that recordset.

Unlink Method to Remove Records

Finally, we delete records with .unlink(). However, we must use it carefully to avoid data loss.

# Example: Remove draft sale orders
draft_orders = self.env['sale.order'].search([('state', '=', 'draft')])
draft_orders.unlink()
  • We call .search() to gather draft orders.
  • Then, we remove them with .unlink().
  • Odoo handles cascade deletes based on model relations.

Explore Odoo API Decorators

Then, we dive into Odoo decorators from odoo.api. Additionally, we show how each decorator modifies method behavior. Next, we present code and explanation side-by-side.

@api.model Decorator for Model-Level Methods

First, use @api.model for methods that don’t rely on a specific record. Moreover, these methods run with the model class as self.

from odoo import models, api

class Partner(models.Model):
    _inherit = 'res.partner'

    @api.model
    def create_from_api(self, vals):
        """
        Create a new partner from external API data.
        """
        return self.create(vals)
  • We decorate create_from_api with @api.model.
  • Then, we call self.create(vals) to create a new record.
  • Odoo returns the new recordset to the caller.

@api.constrains Decorator for Field Validation

Next, we enforce validation rules with @api.constrains. Moreover, Odoo triggers these methods when specified fields change.

from odoo import fields, models, api
from odoo.exceptions import ValidationError

class Person(models.Model):
    _name = 'my.person'
    _description = 'Person with age'

    age = fields.Integer(string='Age')

    @api.constrains('age')
    def _check_age(self):
        for rec in self:
            if rec.age < 0:
                raise ValidationError("Age cannot be negative.")
  • We define age as an integer field.
  • Then, we write _check_age to loop through self recordset.
  • Finally, we raise ValidationError if age is below zero.

@api.depends Decorator for Computed Fields

Then, we compute dynamic fields with @api.depends. Additionally, Odoo recalculates these fields automatically.

from odoo import models, fields, api

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

    total_weight = fields.Float(string='Total Weight', compute='_compute_total_weight')

    @api.depends('order_line.product_id.weight', 'order_line.product_uom_qty')
    def _compute_total_weight(self):
        for order in self:
            total = 0.0
            for line in order.order_line:
                total += line.product_id.weight * line.product_uom_qty
            order.total_weight = total
  • We add total_weight as a computed Float field.
  • Then, we mark dependencies on product weight and quantity.
  • Odoo runs _compute_total_weight whenever those fields change.

@api.onchange Decorator for UI Hooks

Finally, we update form fields on the fly with @api.onchange. Moreover, this hook runs in the web client before saving.

from odoo import models, api

class SaleOrderLine(models.Model):
    _inherit = 'sale.order.line'

    @api.onchange('product_id')
    def _onchange_product_id(self):
        if self.product_id:
            self.name = self.product_id.display_name
            self.price_unit = self.product_id.list_price
  • We react when product_id changes.
  • Then, we set name and price_unit based on chosen product.
  • The form updates instantly without saving.

Advanced Usage: Combining ORM Methods & Decorators

Next, we demonstrate how to combine methods and decorators in a custom model. We build a Contact Sync feature that syncs contacts from an external API and validates data.

from odoo import models, fields, api
from odoo.exceptions import ValidationError
import requests

class ContactSync(models.TransientModel):
    _name = 'contact.sync.wizard'
    _description = 'Wizard to Sync Contacts'

    api_endpoint = fields.Char(string='API Endpoint', required=True)
    company_id = fields.Many2one('res.company', string='Company', required=True)

    @api.model
    def default_get(self, fields_list):
        res = super().default_get(fields_list)
        res['api_endpoint'] = 'https://api.example.com/contacts'
        return res

    def action_sync(self):
        """
        Fetch contacts from external API and create partner records.
        """
        for wizard in self:
            response = requests.get(wizard.api_endpoint)
            data = response.json()
            for contact in data.get('contacts', []):
                vals = {
                    'name': contact.get('name'),
                    'email': contact.get('email'),
                    'company_id': wizard.company_id.id
                }
                partner = self.env['res.partner'].search([('email','=', vals['email'])], limit=1)
                if partner:
                    partner.write(vals)
                else:
                    self.env['res.partner'].create(vals)
  1. Model Definition: We define a transient wizard with api_endpoint.
  2. @api.model Default Values: We override default_get to prefill the API URL.
  3. action_sync Method: We fetch data via requests, then loop through contacts.
  4. Conditional Search & Write/Create: We use .search() to avoid duplicates, and then call .write() or .create().

Best Practices for Odoo ORM Method and Decorator Usage

Moreover, we share best practices to keep your code maintainable.

Use Domain Filters Efficiently

  • First, push filtering to the database with domains.
  • Then, avoid large in-memory recordsets.
  • Finally, combine conditions with | (OR) and & (AND) operators wisely.

Handle Large Datasets Carefully

  • Next, use search_read for paging through results.
  • Moreover, apply limit and offset to avoid performance bottlenecks.
  • Additionally, consider read_group for aggregation on large tables.

Organize Code in Modules

  • First, group related methods in service classes or models.
  • Then, split complex logic into helper methods.
  • Finally, document each method with docstrings for clarity.

Real-World Example: Contact Sync Module

Let’s expand our Contact Sync into a full feature. We add validation, logging, and a schedule hook.

from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
import requests, logging

_logger = logging.getLogger(__name__)

class ContactSync(models.TransientModel):
    _name = 'contact.sync.wizard'
    _description = 'Wizard to Sync Contacts'

    api_endpoint = fields.Char(string='API URL', required=True)
    sync_date = fields.Datetime(string='Last Sync', readonly=True)

    @api.model
    def default_get(self, fields_list):
        res = super().default_get(fields_list)
        res['api_endpoint'] = 'https://api.example.com/contacts'
        return res

    def action_sync(self):
        for wizard in self:
            if not wizard.api_endpoint.startswith('https'):
                raise ValidationError(_("API endpoint must use HTTPS."))
            try:
                resp = requests.get(wizard.api_endpoint, timeout=10)
                resp.raise_for_status()
                contacts = resp.json().get('contacts', [])
                for c in contacts:
                    vals = {
                        'name': c.get('name'),
                        'email': c.get('email'),
                        'phone': c.get('phone'),
                    }
                    partner = self.env['res.partner'].search([('email','=', vals['email'])], limit=1)
                    if partner:
                        partner.write(vals)
                    else:
                        self.env['res.partner'].create(vals)
                wizard.sync_date = fields.Datetime.now()
                _logger.info("Synced %s contacts successfully", len(contacts))
            except Exception as e:
                _logger.error("Failed to sync contacts: %s", e)
                raise ValidationError(_("Failed to sync: %s") % e)

    @api.model
    def cron_sync_contacts(self):
        """
        Scheduled action to run daily at midnight.
        """
        wizards = self.search([], limit=1)
        if wizards:
            wizards.action_sync()
  • We enforce HTTPS with a validation @api.constrains-like guard.
  • We log progress and errors via _logger.
  • We add cron_sync_contacts decorated with @api.model to run as a scheduled task.

Testing and Debugging ORM Methods

Additionally, we outline how to test and debug your ORM code.

  1. Use Odoo Shell ./odoo-bin shell -d your_db >>> partners = env['res.partner'].search([('active', '=', True)]) >>> partners.write({'comment': 'Test update'})
  2. Write Automated Tests from odoo.tests.common import TransactionCase class TestPartnerSync(TransactionCase): def test_sync_creates_partner(self): wizard = self.env['contact.sync.wizard'].create({'api_endpoint': 'https://api.example.com/contacts'}) wizard.action_sync() partners = self.env['res.partner'].search([('email', '=', 'john@example.com')]) self.assertTrue(partners)
  3. Enable SQL Query Logging
    • Edit odoo.conf: log_level = debug_sql
    • Then, you can see all generated SQL statements.

Conclusion and Next Steps

Finally, we recap that Learn Odoo, Common ORM Method and Decorators equips you with the skills to build robust, maintainable modules. We covered:

  • Core ORM methods: .search(), .write(), and .unlink().
  • Key decorators: @api.model, @api.constrains, @api.depends, and @api.onchange.
  • Advanced patterns: combining methods, scheduling, and logging.

Next, we recommend you:

  1. Experiment with each method in a sandbox database.
  2. Build a small custom module applying all decorators.
  3. Read the Odoo ORM documentation for deeper insights.

By following this path, you ensure that your Odoo code stays clear, efficient, and SEO-optimized.


Discover more from teguhteja.id

Subscribe to get the latest posts sent to your email.

Tags:

2 thoughts on “Learn Odoo Common ORM”

  1. Pingback: Odoo ORM Relational Fields - teguhteja.id

  2. Pingback: Advanced Odoo Relational Fields Explained - teguhteja.id

Leave a Reply

WP Twitter Auto Publish Powered By : XYZScripts.com