In real-world Odoo development, we often need to control data
integrity—making sure that users don’t enter invalid or conflicting
data. Odoo offers some main tools for this:
- SQL Constraints
- Python Constraints
These are low-level constraints that work directly with the
PostgreSQL database engine. They are fast and reliable, but not very
flexible.
SQL Constraints
Syntax:
_sql_constraints = [
(
'constraint_name',
'CHECK(sql_condition)',
'Error message when condition is false'
)
]
Example:
_sql_constraints = [
(
'date_order_required',
"CHECK( (state IN ('sale', 'done') AND date_order IS NOT NULL) OR state NOT IN ('sale', 'done') )",
"Confirmed sales orders must have a confirmation date."
)
]
Here,The purpose of the SQL constraint, which was taken from the sale
order, is to confirm that a confirmed sale order contains a
confirmation date. If it doesn't work, an error message appears.
Usually seen in the field declaration part, this Python-based SQL
constraint is specified before to the coding section.
Python Constraints
You define a method and decorate it with @api.constrains. It
automatically triggers when the specified fields are modified.
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class YourModel(models.Model):
_name = 'your.model'
product_id = fields.Many2one('product.product', string='Product')
@api.constrains('product_id')
def check_product_is_not_kit(self):
"""Validation to check whether product is not a kit"""
if self.env['mrp.bom'].search(
['|', ('product_id', 'in', self.product_id.ids), '&',
('product_id', '=', False),
('product_tmpl_id', 'in', self.product_id.product_tmpl_id.ids),
('type', '=', 'phantom')],
count=True
):
raise ValidationError(
_("A product with a kit-type bill of materials cannot have a "
"reordering rule.")
)
A product with a kit-type bill of materials is guaranteed to be
devoid of any reordering rules by the warehouse's SQL constraint. An
error message is displayed if this condition is broken.
Computed Fields
These are fields whose values are automatically calculated based on
other fields. They’re useful for real-time computations like totals,
discounts, or status flags.
from odoo import models, fields, api
class YourModel(models.Model):
_name = 'your.model'
amount = fields.Float(string='Amount')
total = fields.Float(string='Total', compute='_compute_total')
@api.depends('amount')
def _compute_total(self):
for rec in self:
rec.total = rec.amount * 2
Computed fields are not saved in the database unless you explicitly
ask Odoo to store them.
total = fields.Float(string='Total', compute='_compute_total', store=True)
When you use Odoo, computed fields are defined in the same way as
normal fields, with the "compute" attribute naming the function that
does the calculation.
Computed fields are not searchable unless we explicitly configure
them.
If you want users to edit the computed field and push the value back
to the source field:
from odoo import models, fields, api
class YourModel(models.Model):
_name = 'your.model'
amount = fields.Float(string='Amount')
total = fields.Float(string='Total', compute='_compute_total', inverse='_inverse_total')
@api.depends('amount')
def _compute_total(self):
for rec in self:
rec.total = rec.amount * 2
def _inverse_total(self):
for rec in self:
rec.amount = rec.total / 2
Related Fields
In real-world business situations, there are times when we need to
show the value of a field from another related model within the
current model. In these cases, we use related fields by setting the
related attribute to link and display the desired value.
from odoo import models, fields
class YourModel(models.Model):
_name = 'your.model'
partner_id = fields.Many2one('res.partner', string='Partner')
partner_name = fields.Char(related='partner_id.name', string='Partner Name')
Reference Fields
Reference fields in Odoo are used to link any record from any model
dynamically. Unlike Many2one, which links to a fixed model, a
Reference field lets the user choose both the model and the
record—making it perfect for scenarios where the linked object could
be from multiple models.
In the user interface, the user first selects a model (e.g., Sale
Order), after which a second dropdown appears to choose a specific
record from that model; the field then internally stores both the
model and the record ID as a tuple.
Syntax
In a reference field, the selection parameter is used to define the
available models. You need to assign a list of tuples containing the
model's technical name and its display name to this parameter.
from odoo import models, fields
class YourModel(models.Model):
_name = 'your.model'
reference = fields.Reference(
selection=[('sale.order', 'Sale Order'), ('purchase.order', 'Purchase Order')],
string="Document Reference"
)
Or use a dynamic list
from odoo import models, fields, api
class YourModel(models.Model):
_name = 'your.model'
reference = fields.Reference(
selection='get_model_list',
string='Linked Document'
)
@api.model
def get_model_list(self):
models = self.env['ir.model'].search([])
return [(model.model, model.name) for model in models]
The returned values should be in the form of a list.