Object Relational Mapping (ORM) Module Overview

The Object Relational Mapping (ORM) module in Odoo provides a powerful and intuitive way to interact with your database, abstracting away complex SQL queries and offering a high-level, object-oriented approach to data management. This foundation enables robust application development with built-in features for data integrity, security, and performance optimization. The key features of the Odoo ORM include:

  • Hierarchical data structuring capabilities.
  • Rigorous consistency checks and data validation to maintain data quality.
  • Dynamic object metadata that adapts based on its current status.
  • Optimized processing for complex queries, allowing multiple operations simultaneously.
  • Support for defining default field values, simplifying data entry.
  • Advanced permissions optimization for secure data access.
  • Persistent object storage within the PostgreSQL database.
  • Automatic data conversion between Python objects and database types.
  • A multi-level caching system to enhance performance.
  • Two distinct inheritance mechanisms for flexible model design.
  • A comprehensive suite of field types, including:
    • Classical types such as varchar, integer, and boolean.
    • Relational types like one2many, many2one, and many2many for complex data relationships.
    • Functional fields for computed values.

Odoo Models and Their Structure

In Odoo, models are the fundamental building blocks for defining data structures and their associated business logic. Model fields are declared directly as attributes on the model class itself, providing a clear and concise definition. For instance:

from odoo import models, fields

class AModel(models.Model):
    _name = 'a.model.name'
    field1 = fields.Char()

It's important to note a potential conflict: you cannot define a field and a method with the same name. The last definition will silently override the former. By default, a field’s user-visible label is a capitalized version of its technical name, but this can be easily customized using the string parameter:

field2 = fields.Integer(string="Field Label")

For a complete overview of all available field types and their parameters, refer to the detailed fields reference documentation.

Default values for fields can be specified directly as a static value or through a function that computes and returns the default. Here are examples:

name = fields.Char(default="a value")

def _default_name(self):
    return self.get_value()

name = fields.Char(default=lambda self: self._default_name())

BaseModel: The Foundation of Odoo Models

The odoo.models.BaseModel serves as the base class for all Odoo models. When creating new Odoo models, you typically inherit from one of its specialized subclasses, each designed for specific data persistence needs:

  • Model: Used for regular, database-persisted models that store permanent data.
  • TransientModel: Designed for temporary data. Records are stored in the database but are automatically cleaned up at regular intervals.
  • AbstractModel: Intended for abstract super classes that are meant to be inherited by multiple other models. These models do not create their own database tables.

The Odoo system automatically instantiates each model once per database, with these instances representing the available models based on installed modules. Each model instance acts as a "recordset," an ordered collection of records. Individual records are represented as recordsets containing a single record. To prevent a class from being instantiated as a model, the _register attribute can be set to False.

  • _auto = False: Controls whether a database table is automatically created for the model. Defaults to True for Model and TransientModel, and False for AbstractModel. To create a model without a table, always inherit from AbstractModel.
  • _log_access: Determines if the ORM should automatically generate and update Access Log fields. Defaults to the value of _auto.
  • _table = None: Specifies the SQL table name used by the model when _auto is True.
  • _sql_constraints = []: A list of SQL constraints defined as (name, sql_def, message).
  • _register = False: Manages the model's visibility in the registry.
  • _abstract = True: Indicates whether the model is an abstract model.
  • _transient = False: Indicates whether the model is a transient model.
  • _name = None: The model's technical name, following a dot-notation (e.g., module.model_name).
  • _description = None: The model's informal, user-friendly name.
  • _inherit = (): Specifies Python-inherited models. It can be a string for a single parent model (if _name is unset, extending in-place) or a list of strings for multiple parent models (if _name is set, creating a new model).
  • _inherits = {}: A dictionary mapping parent model names to the foreign key fields used for composition-based inheritance. This mechanism allows the new model to expose fields of inherited models without storing their values directly, instead referencing them on the linked record. Note: if multiple fields with the same name exist in _inherits-ed models, the inherited field will correspond to the last one in the list order.
  • _rec_name = None: The field used for labeling records, defaulting to name.
  • _order = 'id': The default field used for sorting search results.
  • _check_company_auto = False: When set to True, calls _check_company on write and create operations to ensure company consistency across relational fields with check_company=True.
  • _parent_name = 'parent_id': The many2one field designated as the parent field for hierarchical structures.
  • _parent_store = False: When True, computes a parent_path field and sets up an indexed storage for the record tree structure, enabling faster hierarchical queries using child_of and parent_of domain operators.
  • _fold_name = 'fold': The field used to determine folded groups in Kanban views.

AbstractModel

The odoo.models.AbstractModel is an alias of odoo.models.BaseModel and serves as the foundation for models that are not intended to be persisted directly in the database. These are typically used for sharing common functionalities or fields across multiple concrete models without creating their own tables.

Model

The odoo.models.Model is the primary super-class for all regular, database-persisted Odoo models. When you define a new model that needs to store data permanently, you inherit from this class. The Odoo system instantiates each Model class once per database where its module is installed.

  • _auto = True: Automatically creates a database table for the model.
  • _abstract = False: Indicates that this is a concrete, not abstract, model.

TransientModel

The odoo.models.TransientModel is a specialized model super-class for records designed for temporary persistence. These records are stored in the database but are subject to regular automated vacuum-cleaning. TransientModels also feature a simplified access rights management: users can create their own records and only access those they created, while the superuser maintains unrestricted access.

  • _transient_max_count = 0: Sets the maximum number of transient records. A value of 0 indicates no limit.
  • _transient_max_hours = 1.0: Defines the maximum idle lifetime (in hours) for transient records. A value of 0 means no time limit.
  • _transient_vacuum(): This method is responsible for cleaning up old transient records based on the _transient_max_count and _transient_max_hours conditions. The actual cleaning occurs approximately every 5 minutes, allowing this method to be called frequently (e.g., upon new record creation) without excessive overhead. For instance, if max_hours = 0.2 (12 minutes) and max_count = 20, the system intelligently removes older records to stay within limits, while preserving recently created/modified ones.

Fields: Defining Data Attributes

The odoo.fields.Field descriptor encapsulates the definition and management of data attributes on Odoo records. When instantiating a field, various parameters can be provided to customize its behavior and appearance:

  • string (str): The user-visible label for the field. If not set, it defaults to a capitalized version of the field's technical name.
  • help (str): A tooltip providing additional information to users.
  • readonly (bool): If True, the field is read-only in the UI (default: False). Programmatic assignments still function.
  • required (bool): If True, the field's value is mandatory (default: False).
  • index (str): Specifies whether and how the field is indexed in the database (e.g., "btree", "btree_not_null", "trigram"). Defaults to None (no index). This parameter has no effect on non-stored or virtual fields.
  • default (value or callable): The default value for the field. Can be a static value or a function returning a value. Use default=None to explicitly discard any default.
  • groups (str): A comma-separated list of XML IDs of groups, restricting field access to users belonging to those groups.
  • company_dependent (bool): If True, the field's value varies depending on the current company. Values are stored as ir.property records, allowing for company-specific data.
  • copy (bool): Determines if the field's value is copied when a record is duplicated (default: True for normal fields, False for one2many and computed fields).
  • store (bool): If True, the field is stored in the database (default: True for most fields, False for computed fields). Setting store=True for computed fields automatically enables searching.
  • group_operator (str): The aggregate function used by read_group() when grouping on this field (e.g., 'count', 'sum', 'avg').
  • group_expand (str): A function used to expand read_group results, providing available choices for selection fields or additional records for many2one fields.

Computed Fields

Computed fields derive their values from other fields or logic, rather than being stored directly in the database. They offer powerful flexibility in data representation:

  • compute (str): The name of a method that calculates the field's value.
  • precompute (bool): If True, the field is computed before record insertion in the database (default: False). This can be beneficial for fields created in batch but can be counterproductive for single record creations if not carefully managed.
  • compute_sudo (bool): If True, the field is recomputed as a superuser to bypass access rights (default: True for stored, False for non-stored fields).
  • recursive (bool): If True, indicates that the field has recursive dependencies (e.g., parent_id.X), ensuring correct recomputation in hierarchical structures.
  • inverse (str): The name of an optional method that reverses the computation, allowing values to be written to a computed field.
  • search (str): The name of an optional method that implements searching on the field, returning a search domain.
  • related (str): A dot-separated sequence of field names, automatically making the field's value a direct copy of the related field.
  • default_export_compatible (bool): If True, the field must be exported by default in an import-compatible export.

Basic Fields

Odoo provides a set of fundamental field types to handle common data requirements:

  • odoo.fields.Boolean: Encapsulates a Python bool value.
  • odoo.fields.Char: A basic string field, typically displayed as a single-line input.
    • size (int): Maximum storage size for values.
    • trim (bool): Determines if the value is trimmed by the web client (default: True).
    • translate (bool or callable): Enables translation of field values.
  • odoo.fields.Float: Encapsulates a Python float.
    • digits (tuple of int or str): Defines the precision digits as (total, decimal) or a string referencing a DecimalPrecision record.

    The Float class offers static helper methods for precise comparisons and rounding, crucial when dealing with quantities and units of measure:

    fields.Float.round(self.product_uom_qty, precision_rounding=self.product_uom_id.rounding)
    fields.Float.is_zero(self.product_uom_qty, precision_rounding=self.product_uom_id.rounding)
    field.Float.compare(self.product_uom_qty, self.qty_done, precision_rounding=self.product_uom_id.rounding)

    The compare() method returns 0 if values are equal, < 0 if the first is lower, and > 0 if the first is greater.

  • odoo.fields.Integer: Encapsulates a Python int.

Advanced Fields

Beyond basic types, Odoo offers specialized fields for richer content:

  • odoo.fields.Binary: Encapsulates binary content, such as files.
    • attachment (bool): Determines if the field is stored as an ir_attachment or directly in a model column (default: True).
  • odoo.fields.Html: Encapsulates HTML code content.
    • sanitize (bool): If True, the value is sanitized (default: True).
    • sanitize_overridable (bool): Allows users in base.group_sanitize_override to bypass sanitation (default: False).
    • sanitize_tags (bool): Sanitizes HTML tags based on a whitelist (default: True).
    • sanitize_attributes (bool): Sanitizes attributes based on a whitelist (default: True).
    • sanitize_style (bool): Sanitizes style attributes (default: False).
    • strip_style (bool): Removes style attributes instead of sanitizing (default: False).
    • strip_classes (bool): Removes class attributes (default: False).
  • odoo.fields.Image: Encapsulates an image, extending Binary. Images are resized if they exceed specified maximum dimensions while maintaining aspect ratio.
    • max_width (int): Maximum image width (default: 0, no limit).
    • max_height (int): Maximum image height (default: 0, no limit).
    • verify_resolution (bool): Verifies image resolution against a maximum limit (default: True). If no max_width/max_height is set and verify_resolution is False, a Binary field might be more appropriate.
  • odoo.fields.Monetary: Encapsulates a float value expressed in a specific res_currency. Decimal precision and currency symbols are derived from the associated currency field.
    • currency_field (str): The name of the Many2one field holding the res_currency (default: 'currency_id').
  • odoo.fields.Selection: Encapsulates an exclusive choice from a predefined list of values.
    • selection (list of tuples or callable or str): Specifies possible values as (value, label) pairs, a model method, or a method name. This attribute is mandatory unless the field is related or extended.
    • selection_add (list of tuples): Extends the selection for overridden fields, allowing new options or reordering existing ones.
    • ondelete: Provides a fallback mechanism for overridden fields with selection_add, defining actions like 'set null', 'cascade', 'set default', 'set VALUE', or a callable.
  • odoo.fields.Text: Similar to Char but designed for longer content, typically displayed as a multi-line text box without a size limit.
    • translate (bool or callable): Enables translation of field values.

Date and Datetime Fields

Dates and Datetimes are critical fields in business applications, and proper handling is essential to avoid subtle bugs. Odoo provides specific field types and helper methods for their management:

When assigning values to Date/Datetime fields, valid options include:

  • A Python date or datetime object.
  • A string formatted as YYYY-MM-DD for Date fields, or YYYY-MM-DD HH:MM:SS for Datetime fields.
  • False or None to clear the value.

Helper methods are available for type conversion:

  • to_date(): Converts a value to a datetime.date object.
  • to_datetime(): Converts a value to a datetime.datetime object.

For example, to parse dates from external sources:

fields.Date.to_date(self._context.get('date_from'))

Comparison Best Practices:

  • Date fields should only be compared with date objects.
  • Datetime fields should only be compared with datetime objects.

Warning: Comparing string representations of dates and datetimes is highly discouraged, as it can lead to unexpected results due to lexicographical ordering.

Common operations like addition, subtraction, and fetching period boundaries are available via Date and Datetime classes, or by importing odoo.tools.date_utils. Datetime fields are stored in UTC as timestamp without timezone in the database; timezone conversions are handled client-side.

  • odoo.fields.Date: Encapsulates a Python date object.
    • start_of(value, granularity): Returns the start of a specified time period (year, quarter, month, week, day, hour).
    • end_of(value, granularity): Returns the end of a specified time period.
    • add(value, *args, **kwargs): Adds a relativedelta to the value.
    • subtract(value, *args, **kwargs): Subtracts a relativedelta from the value.
    • today(): Returns the current day in ORM-expected format.
    • context_today(record, timestamp=None): Returns the current date in the client's timezone.
    • to_date(value): Converts a value to a date object.
    • to_string(value): Converts a date or datetime object to a string in the server's date format.
  • odoo.fields.Datetime: Encapsulates a Python datetime object.
    • Includes start_of, end_of, add, subtract methods similar to Date.
    • now(): Returns the current day and time in ORM-expected format.
    • today(): Returns the current day at midnight (00:00:00).
    • context_timestamp(record, timestamp): Converts a naive UTC datetime to a timezone-aware datetime in the client's timezone.
    • to_datetime(value): Converts an ORM value into a datetime object.
    • to_string(value): Converts a datetime or date object to a string in the server's datetime format.

Relational Fields

Relational fields define connections between different models, forming the backbone of complex data structures in Odoo:

  • odoo.fields.Many2one: Represents a many-to-one relationship, where many records in the current model can be linked to one record in the target model. The field's value is a recordset of size 0 or 1.
    • comodel_name (str): The name of the target model (mandatory unless related or extended).
    • domain: An optional search domain to filter candidate values on the client side.
    • context (dict): An optional context to use when handling the field on the client side.
    • ondelete (str): Defines the action when the referred record is deleted ('set null', 'restrict', 'cascade').
    • auto_join (bool): Controls whether JOINs are generated during searches through this field (default: False).
    • delegate (bool): Set to True to make fields of the target model accessible from the current model (corresponds to _inherits).
    • check_company (bool): Marks the field for company consistency verification via _check_company().
  • odoo.fields.One2many: Represents a one-to-many relationship. The field's value is a recordset of all records in the comodel_name where the inverse_name field points back to the current record.
    • comodel_name (str): The name of the target model.
    • inverse_name (str): The name of the inverse Many2one field in the target model.
    • domain: An optional search domain for candidate values.
    • context (dict): An optional context for client-side handling.
    • auto_join (bool): Controls JOIN generation during searches (default: False).

    comodel_name and inverse_name are mandatory, except for related fields or field extensions.

  • odoo.fields.Many2many: Represents a many-to-many relationship, allowing multiple records in the current model to be linked to multiple records in the target model. The field's value is a recordset.
    • comodel_name (str): The name of the target model (mandatory unless related or extended).
    • relation (str): Optional name of the intermediary table storing the relation.
    • column1 (str): Optional name of the column in the relation table referring to "these" records.
    • column2 (str): Optional name of the column in the relation table referring to "those" records.

    If relation, column1, and column2 are not provided, names are automatically generated from model names. Having multiple fields with implicit relation parameters on the same model with the same comodel is generally not supported by the ORM, as they would attempt to use the same table.

    • domain: An optional search domain for candidate values.
    • context (dict): An optional context for client-side handling.
    • check_company (bool): Marks the field for company consistency verification.
  • odoo.fields.Command: A utility class for manipulating One2many and Many2many fields. These fields expect specific commands (represented as 3-element tuples) to manage their relations. It is recommended to use the provided classmethods for crafting these commands in Python.
    • CREATE = 0: Create new records in the comodel and link them.
    • UPDATE = 1: Write values on an existing related record.
    • DELETE = 2: Remove a related record from the database and its relation.
    • UNLINK = 3: Remove the relation between self and the related record. The related record might be deleted if ondelete='cascade'.
    • LINK = 4: Add a relation between self and an existing record.
    • CLEAR = 5: Remove all records from the relation with self (behaves like unlink on all records).
    • SET = 6: Replace current relations with a new set of given records (behaves like unlink on removed relations and link on new ones).

    Class methods like create(_values: dict), update(_id: int, _values: dict), delete(_id: int), unlink(_id: int), link(_id: int), clear(), and set(_ids: list) are available to generate these command triples.

Pseudo-Relational Fields

These fields establish relationships without enforcing a foreign key constraint in the database, offering more flexibility but requiring careful management:

  • odoo.fields.Reference: A pseudo-relational field without a database foreign key. The field value is stored as a string in the format "res_model,res_id".
  • odoo.fields.Many2oneReference: Another pseudo-relational field without a foreign key, storing an integer ID. Unlike Reference fields, the model name must be specified in a separate Char field, whose name is linked via the model_field attribute of the Many2oneReference field.
    • model_field (str): The name of the Char field storing the model name.

Computed Fields Explained

Fields can be computed dynamically rather than being stored directly in the database, using the compute parameter. The computation method must assign the calculated value to the field and, if it relies on other fields, these dependencies should be specified using @api.depends():

from odoo import api
total = fields.Float(compute='_compute_total')

@api.depends('value', 'tax')
def _compute_total(self):
    for record in self:
        record.total = record.value + record.value * record.tax
  • Dependencies can be expressed using dotted paths for sub-fields (e.g., 'line_ids.value').
  • By default, computed fields are not stored. Setting store=True will persist them in the database and automatically enable searching.
  • Searching on a computed field can also be enabled by providing a search parameter, which is a method name returning a search domain.
  • Computed fields are read-only by default. The inverse parameter specifies a function to reverse the computation, allowing values to be set on the computed field.
  • Multiple fields can share the same compute method, simultaneously calculating and setting their values within a single pass.

Warning: While sharing a compute method is acceptable, using the same inverse method for multiple fields is generally not recommended. During an inverse computation, all fields sharing that inverse are protected, potentially returning default values if their actual values are not cached, which can lead to unexpected behavior.

Automatic Fields

Odoo models automatically include several fields that provide essential information about records:

  • Model.id: The unique identifier for each record. When a recordset contains a single record, it returns that record's ID.
  • Model.display_name: A character field that represents the record's name as displayed in the web client. By default, it uses the value of _rec_name, but its behavior can be customized by overriding _compute_display_name.

Access Log Fields

These fields are automatically managed when _log_access is enabled, tracking creation and modification details. By default, _log_access mirrors the setting of _auto.

  • Model.create_date: A Datetime field storing the timestamp when the record was created.
  • Model.create_uid: A Many2one field linking to the res.users record of the user who created the record.
  • Model.write_date: A Datetime field storing the timestamp of the last update to the record.
  • Model.write_uid: A Many2one field linking to the res.users record of the user who last updated the record.

Warning: The _log_access attribute must be enabled on TransientModels to ensure proper tracking and cleanup.

Reserved Field Names

Certain field names are reserved in Odoo for specific behaviors and functionalities:

  • Model.name: A Char field that serves as the default value for _rec_name, used for displaying records where a representative name is needed.
  • Model.active: A Boolean field that toggles the global visibility of a record. If set to False, the record is typically invisible in most searches and listings.
    • toggle_active(): Inverses the value of active for the records in the current recordset.
    • action_archive(): Sets active to False for active records.
    • action_unarchive(): Sets active to True for inactive records.
  • Model.state: A Selection field representing the lifecycle stages of an object, often used with the states attribute on other fields.
  • Model.parent_id: A Many2one field (default for _parent_name) used to organize records in a tree structure, enabling child_of and parent_of operators in domains.
  • Model.parent_path: A Char field used when _parent_store is True. It stores a value reflecting the tree structure of _parent_name and optimizes hierarchical search operators. It must be declared with index=True for optimal performance.
  • Model.company_id: A Many2one field to res_company, crucial for Odoo's multi-company behavior. It defines whether a record is shared across companies or accessible only by users of a specific company, and is used by _check_company for consistency.

Recordsets: Interacting with Records

In Odoo, interactions with models and individual records are primarily performed through recordsets. A recordset is an ordered collection of records belonging to the same model. It is important to understand that while the name suggests uniqueness, recordsets can currently contain duplicates, though this behavior may change in future versions.

Methods defined on a model are always executed on a recordset, with self referring to that recordset. Iterating over a recordset yields new recordsets, each containing a single record (singletons), similar to how iterating a Python string yields single characters.

class AModel(models.Model):
    _name = 'a.model'
    def a_method(self):
        # self can range from an empty set to all records in the database
        self.do_operation()

def do_operation(self):
    print(self) # => a.model(1, 2, 3, 4, 5)
    for record in self:
        print(record) # => a.model(1), then a.model(2), then a.model(3), ...

Field Access

Recordsets offer an "Active Record" interface, allowing model fields to be read and written directly as attributes. This provides a Pythonic and intuitive way to interact with your data.

>>> record.name
Example Name
>>> record.company_id.name
Company Name
>>> record.name = "Bob"
>>> field = "name"
>>> record[field]
Bob

Warning: Attempting to read a non-relational field on a recordset containing multiple records will raise an error. For such cases, use the mapped() method. Accessing relational fields (Many2one, One2many, Many2many) always returns a recordset, which will be empty if the field is not set.

Record Cache and Prefetching

To optimize performance and minimize database queries, Odoo maintains a cache for record fields. This means that after an initial access, subsequent accesses to the same field on the same record retrieve the value from cache, avoiding redundant database calls.

record.name             # First access reads value from database
record.name             # Second access gets value from cache

Odoo also employs a prefetching mechanism. When a field is read on a specific record, the ORM intelligently fetches that field (and other common fields) for a larger group of records (the original recordset from which the current record was iterated). This significantly reduces the number of database queries, especially in loops. For example, iterating over 1000 records and accessing two simple fields would typically result in only one database query due to prefetching.

for partner in partners:
    print partner.name          # First pass prefetches 'name' and 'lang' for all 'partners'
    print partner.lang

Prefetching also extends to secondary records. When relational fields are read, the related records are subscribed for future prefetching. Accessing one of these secondary records then prefetches all other secondary records from the same model, further optimizing data retrieval. This can reduce multiple queries to just a couple, for instance, one for partners and another for countries.

countries = set()
for partner in partners:
    country = partner.country_id        # First pass prefetches all partners
    countries.add(country.name)         # First pass prefetches all countries

For scenarios where the automatic prefetching heuristics may not be ideal, the search_fetch() and fetch() methods can be explicitly used to populate the cache of records, ensuring optimal performance.

Method Decorators in Odoo API

The Odoo API module provides several method decorators to define environments and enhance the behavior of model methods. These decorators ensure that methods operate within the correct context and trigger appropriate ORM functionalities.

  • odoo.api.model(_method_): Decorates a record-style method where only the model itself is relevant, not the specific records within the recordset.
@api.model
def method(self, args):
    # ... logic here ...
odoo.api.constrains(_*args_): Decorates a constraint checker. Arguments are field names, and the method is invoked when any of these fields are modified on the records. It should raise a ValidationError if the check fails.
@api.constrains('name', 'description')
def _check_description(self):
    for record in self:
        if record.name == record.description:
            raise ValidationError("Fields name and description must be different")

Warning: @constrains only supports simple field names, not dotted names. It is triggered only when declared fields are part of a create or write call; for guaranteed invocation, overriding create is necessary.

odoo.api.depends(_*args_): Specifies field dependencies for a computed method, ensuring recomputation when any dependent field changes. Arguments are dot-separated field names.
pname = fields.Char(compute='_compute_pname')

@api.depends('partner_id.name', 'partner_id.is_company')
def _compute_pname(self):
    for record in self:
        if record.partner_id.is_company:
            record.pname = (record.partner_id.name or "").upper()
        else:
            record.pname = record.partner_id.name
odoo.api.onchange(_*args_): Decorates an onchange method for specified fields. It is called when one of the fields is modified in a form view, allowing automatic updates to other fields on a pseudo-record.
@api.onchange('partner_id')
def _onchange_partner(self):
    self.message = "Dear %s" % (self.partner_id.name or "")
    return {
        'warning': {'title': "Warning", 'message': "What is this?", 'type': 'notification'},
    }

Danger: Calling CRUD methods on the pseudo-recordset within an @onchange method results in undefined behavior. Simply set the record's fields directly. Also, @onchange does not support modifying one2many or many2many fields on itself due to web client limitations.

odoo.api.returns(_model_, _downgrade=None_, _upgrade=None_): Decorates methods that return instances of a specified model. It adapts the method's output to either traditional (ID, IDs, False) or record-style (recordset) based on the call context. odoo.api.autovacuum(_method_): Marks a method to be executed by the daily vacuum cron job, typically for garbage collection tasks that don't require a dedicated cron. odoo.api.depends_context(_*args_): Specifies context dependencies for non-stored computed methods. The method will recompute if any of the specified context keys change. odoo.api.model_create_multi(_method_): Decorates a method that takes a list of dictionaries to create multiple records, allowing it to be called with either a single dict or a list. odoo.api.ondelete(_*_, _at_uninstall_): Marks a method to be executed during unlink() operations. This allows for client-side errors to prevent deletion based on business logic, with an option to bypass these checks during module uninstallation (at_uninstall=False is common).
@api.ondelete(at_uninstall=False)
def _unlink_if_user_inactive(self):
    if any(user.active for user in self):
        raise UserError("Can't delete an active user!")

Danger: The at_uninstall parameter should only be True if the check is critical even during module uninstallation (e.g., preventing deletion of the default language).

The Odoo Environment

The odoo.api.Environment object is central to the ORM, encapsulating critical contextual data used for all database operations and business logic. It provides a consistent context for interacting with models and records.

  • cr: The current database cursor, used for direct SQL queries.
  • uid: The ID of the current user, essential for access rights enforcement.
  • context: A dictionary containing arbitrary metadata and contextual information that can influence method behavior.
  • su: A boolean indicating whether the environment is currently in superuser mode, bypassing access rights checks.

The environment allows access to the registry (mapping model names to model instances), holds a cache for records, and manages recomputations for fields. When creating a recordset from another, the environment is automatically inherited. You can use the environment to get an empty recordset of any model and then query that model.

>>> records.env
<Environment object ...>
>>> records.env.uid
3
>>> records.env.user
res.user(3)
>>> records.env.cr
<Cursor object ...>

>>> self.env['res.partner']
res.partner()
>>> self.env['res.partner'].search([('is_company', '=', True), ('customer', '=', True)])
res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74)

Useful Environment Methods

The Environment object provides several convenient properties and methods for accessing contextual data:

  • Environment.lang: Returns the current language code as a string.
  • Environment.user: Returns the current user as a res.users record, typically in sudoed mode.
  • Environment.company: Returns the current company as a res.company record. If allowed_company_ids is not specified in the context, it defaults to the current user's main company. Accessing companies in sudo mode bypasses sanity checks.
  • Environment.companies: Returns a recordset of all companies enabled for the current user. Similar to company, it defaults to the user's allowed companies and bypasses checks in sudo mode.
  • Environment.ref(_xml_id_, _raise_if_not_found=True_): Retrieves the record corresponding to a given XML ID (e.g., 'module.id'). Raises a ValueError if not found and raise_if_not_found is True.
  • Environment.is_superuser(): Checks if the environment is in superuser mode.
  • Environment.is_admin(): Checks if the current user has the "Access Rights" group or is in superuser mode.
  • Environment.is_system(): Checks if the current user has the "Settings" group or is in superuser mode.

Altering the Environment

You can create new versions of a recordset attached to a modified environment using specific methods:

  • Model.with_context([context][, **overrides]): Returns a new recordset with an extended context. It either merges overrides into a provided context or into the current context.
# current context is {'key1': True}
r2 = records.with_context({}, key2=True)
# -> r2._context is {'key2': True}
r2 = records.with_context(key2=True)
# -> r2._context is {'key1': True, 'key2': True}
Model.with_user(_user_): Returns a new recordset attached to the specified user, typically in non-superuser mode unless the user is the superuser. Model.with_company(_company_): Returns a new recordset where the environment's main company is set to the provided company, and env.companies is updated to include it.

Warning: Using an unauthorized company for the current user may trigger an AccessError unless performed in a sudoed environment.

Model.with_env(_env_): Returns a new recordset attached to an entirely new or different environment. Model.sudo([_flag=True_]): Returns a new recordset with superuser mode enabled or disabled. This bypasses access rights checks without changing the current user.

Warning: Using sudo can bypass record rules and potentially mix isolated records, leading to unexpected results in methods that select a single record (e.g., default company, Bill of Materials selection).

SQL Execution and Database Interaction

While the ORM handles most database interactions, there are cases where direct SQL execution is necessary for complex queries or performance optimization. The cr attribute of the environment provides direct access to the database cursor.

self.env.cr.execute("some_sql", params)

Warning: Executing raw SQL bypasses the ORM's security rules. Always sanitize user input in your queries and prefer ORM utilities if direct SQL is not strictly necessary.

Odoo provides a robust odoo.tools.SQL wrapper for safely building and combining SQL queries:

  • odoo.tools.SQL(_code: str | odoo.tools.sql.SQL = '', /_ , *args_, **kwargs_): Wraps SQL code and its parameters. It supports both positional (%s) and named (%(name)s) arguments. The wrapper is composable, meaning arguments can be other SQL objects, simplifying the management of parameters.
sql = SQL("UPDATE TABLE foo SET a = %s, b = %s", 'hello', 42)
cr.execute(sql)

sql = SQL(
    "UPDATE TABLE %s SET %s",
    SQL.identifier(tablename),
    SQL("%s = %s", SQL.identifier(columnname), value),
)
join(_args: Iterable_): Joins multiple SQL objects or parameters using self as a separator. classmethod identifier(_name: str_, _subname: Optional[str] = None_): Returns an SQL object representing a safely quoted identifier.

Flushing: Ensuring Database Consistency

Odoo models do not always perform database updates immediately; recomputations and some database writes are delayed for performance. Before executing direct SQL queries that rely on recent changes, it is crucial to "flush" these pending operations to the database. This ensures that the database contains the most up-to-date data. It's recommended to be specific when flushing to optimize performance.

# Make sure 'partner_id' is up-to-date in database
self.env['model'].flush_model(['partner_id'])
self.env.cr.execute(SQL("SELECT id FROM model WHERE partner_id IN %s", ids))
ids = [row[0] for row in self.env.cr.fetchall()]
  • Environment.flush_all(): Flushes all pending computations and updates across all models to the database.
  • Model.flush_model(_fnames=None_): Processes pending computations and database updates for a specific model. If fnames is provided, it guarantees that at least those fields are flushed.
  • Model.flush_recordset(_fnames=None_): Processes pending computations and database updates for the records in the current recordset. If fnames is provided, it guarantees that at least those fields on these specific records are flushed.

Invalidating Caches: Maintaining Coherence

After altering the database directly via raw SQL (e.g., CREATE, UPDATE, DELETE), it is essential to invalidate the ORM's caches to prevent inconsistencies with subsequent model operations. This ensures that the ORM re-reads fresh data from the database.

# Make sure 'state' is up-to-date in database
self.env['model'].flush_model(['state'])
self.env.cr.execute("UPDATE model SET state=%s WHERE state=%s", ['new', 'old'])
# Invalidate 'state' from the cache
self.env['model'].invalidate_model(['state'])

Similar to flushing, it's best to be specific when invalidating caches to maximize performance.

  • Environment.invalidate_all(_flush=True_): Invalidates the cache of all records. By default, it flushes pending updates first to maintain consistency.
  • Model.invalidate_model(_fnames=None_, _flush=True_): Invalidates the cache of all records for a given model. If fnames is provided, only those specific fields are invalidated.
  • Model.invalidate_recordset(_fnames=None_, _flush=True_): Invalidates the cache of specific records within a recordset. If fnames is provided, only those fields on those records are invalidated.

When computed field dependencies are modified directly in the database, the ORM needs to be informed for correct recomputation. After invalidating the cache, use the modified method to notify the framework about the changes.

# Example: Update and notify framework of changes
self.env['model'].flush_model(['state'])
self.env.cr.execute("UPDATE model SET state=%s WHERE state=%s RETURNING id", ['new', 'old'])
ids = [row[0] for row in self.env.cr.fetchall()]

records = self.env['model'].browse(ids)
records.invalidate_recordset(['state'])
records.modified(['state'])
  • Model.modified(_fnames_, _create=False_, _before=False_): Notifies the ORM that specified fields on records in self have been or will be modified. This triggers necessary cache invalidations and prepares for the recomputation of dependent stored fields.

Common ORM Methods

The Odoo ORM provides a standard set of methods for interacting with records, covering creation, retrieval, updating, and deletion operations.

Create and Update Records

  • Model.create(_vals_list_) -> records: Creates new records for the model. Values are initialized from vals_list (a list of dictionaries) and potentially from default_get(). For backward compatibility, vals_list can be a single dictionary.

    Raises: AccessError, ValidationError, ValueError, UserError.

  • Model.copy(_default=None_): Duplicates a record, optionally overriding values with those provided in the default dictionary.
  • Model.default_get(_fields_list_) -> default_values: Returns default values for the specified fields, considering context, user defaults, and model definitions.
  • Model.name_create(_name_) -> record: Creates a new record using only a display name. The record is initialized with default values, returning a (id, display_name) pair.
  • Model.write(_vals_): Updates all records in the current recordset with the provided values in the vals dictionary.

    Raises: AccessError, ValidationError, UserError.

    Field values for write():

    • Numeric fields: Corresponding Python numeric type.
    • Boolean: Python bool.
    • Selection: Matching selection value (str or int).
    • Many2one: Database ID of the linked record.
    • One2many or Many2many: A list of Command tuples to manipulate the relation.
    • Date and Datetime: Python date/datetime objects or UTC-formatted strings.
    • Other non-relational fields: String values.

Search and Read Operations

Odoo provides comprehensive methods for searching and reading records from the database.

  • Model.browse([_ids_]) -> records: Returns a recordset for the specified IDs in the current environment.
self.browse([7, 18, 12])
res.partner(7, 18, 12)
Model.search(_domain_[, _offset=0_][, _limit=None_][, _order=None_]): Searches for records matching a given search domain, with optional offset, limit, and order parameters. Returns a recordset. This is a high-level method, implementing its logic via _search().

Raises: AccessError.

Model.search_count(_domain_[, _limit=None_]) -> int: Returns the number of records matching the provided search domain. Model.search_fetch(_domain, field_names[, offset=0][, limit=None][, order=None]): Combines searching and fetching fields into a single optimized operation, populating the cache with the specified field values. Model.name_search(_name='', _args=None_, _operator='ilike'_, _limit=100_) -> records: Searches for records whose display name matches a pattern, optionally constrained by additional search arguments. Returns a list of (id, display_name) pairs. Model.fetch(_field_names_): Ensures that the specified fields are loaded into memory for the records in the current recordset, optimizing data access. Non-stored fields are largely ignored, except for their stored dependencies.

Raises: AccessError.

Model.read([_fields_]): Reads the values of the requested fields for records in the current recordset, returning a list of dictionaries (one per record).

Raises: AccessError, ValueError.

Model._read_group(_domain_, _groupby=()_, _aggregates=()_, _having=()_, _offset=0_, _limit=None_, _order=None_): Retrieves field aggregations grouped by specified fields, filtered by a domain. Returns a list of tuples containing group and aggregate values. Model.read_group(_domain_, _fields_, _groupby_, _offset=0_, _limit=None_, _orderby=False_, _lazy=True_): Provides a higher-level grouping function, returning a list of dictionaries with grouped field values, domain information, and context.

Fields (Metadata)

  • Model.fields_get([_allfields_][, _attributes_]): Returns the definition of each field, including inherited fields. Field strings, help text, and selection options are translated. Returns a dictionary mapping field names to their attributes and values.

Search Domains

A search domain in Odoo is a powerful mechanism for filtering records. It is a list of criteria, each expressed as a triple (field_name, operator, value):

  • field_name (str): A field name of the current model, or a relationship traversal using dot-notation (e.g., 'street' or 'partner_id.country').
  • operator (str): An operator for comparing field_name with value. Common operators include:
    • =: equals to
    • !=: not equals to
    • >, >=, <, <=: comparison operators
    • =?: unset or equals to (None or False behaves like =)
    • =like, like, not like, ilike, not ilike, =ilike: pattern matching operators (case-sensitive or insensitive).
    • in, not in: checks if the field value is in (or not in) a list of items.
    • child_of, parent_of: checks hierarchical relationships.
    • any, not any: matches if any (or no) record in a relational field traversal satisfies a nested domain.
  • value: The value to compare against the field, its type must be comparable with the field.

Domain criteria can be combined using logical operators in prefix form:

  • '&': Logical AND (default behavior for consecutive criteria). Arity 2.
  • '|': Logical OR. Arity 2.
  • '!': Logical NOT. Arity 1.

Example: Search for partners named 'ABC' with a phone or mobile number containing '7620':

[('name', '=', 'ABC'),
 '|', ('phone','ilike','7620'), ('mobile', 'ilike', '7620')]

Example: Search sales orders to invoice that have at least one line with an out-of-stock product:

[('invoice_status', '=', 'to invoice'),
 ('order_line', 'any', [('product_id.qty_available', '<=', 0)])]

Unlink (Delete Records)

  • Model.unlink(): Deletes the records within the current recordset.

    Raises: AccessError if the user lacks permissions, or UserError if the record is a default property for other records.

Recordset Information

Several attributes and methods provide essential information about recordsets:

  • Model.ids: Returns a list of the actual database IDs corresponding to the records in self.
  • odoo.models.env: Returns the Environment object associated with the given recordset.
  • Model.exists() -> records: Returns the subset of records in self that actually exist in the database. New records are always considered to exist by convention.
  • Model.ensure_one(): Verifies that the current recordset contains exactly one record. Raises a ValueError if this condition is not met.
  • Model.get_metadata(): Returns a list of dictionaries containing metadata (ID, creation/modification UIDs and dates, XML IDs, noupdate status) for each requested record.

Recordset Operations

Recordsets are immutable, but they can be combined and transformed using various set operations, which return new recordsets. While standard Python tools like map() and sorted() can be used, they often return lists or iterators, breaking the recordset's ability to call further methods or use set operations. Therefore, Odoo provides specialized recordset methods that maintain the recordset type.

  • Set membership: record in set and record not in set check for presence.
  • Set comparison: set1 <= set2 (subset), set1 < set2 (strict subset), set1 >= set2 (superset), set1 > set2 (strict superset).
  • Set algebra: set1 | set2 (union), set1 & set2 (intersection), set1 - set2 (difference).

Filter Records

  • Model.filtered(_func_): Returns a new recordset containing only the records from self that satisfy the given function or dot-separated field sequence.
# Keep records whose company
War diese Antwort hilfreich? 0 Benutzer fanden dies hilfreich (0 Stimmen)