Constraints
Any real-world business scenario has a chance that users provide inaccurate data. Constraints allow us to stop users from submitting inaccurate data. In order to prevent inaccurate data from being recorded before the record is saved, constraints are limits set on the record.
Two approaches
The two tools given by Odoo for automatically validating invariants are Python Constraints and SQL Constraints.
SQL Constraints:
SQL Constraints are defined on the model using the class attribute _sql_constaints. This belongs to the part of PostgreSQL.
Syntax for writing SQL Constraints:
_sql_constraints = [(name, sql_def, message)]
name: SQL Constraints name.
sql_def: PostgreSQL syntax of the constraint.
message: error message.
For example,
sql_constraints = [('date_order_conditional_required', "CHECK( (state IN ('sale', 'done') AND date_order IS NOT NULL) OR state NOT IN ('sale', 'done') )", "A confirmed sales order requires a confirmation date."),]
This is the sql_constraints taken from the sale order. It aims to verify the confirmed sale order has a confirmation date. If the sql_constraints fails, then it will show an error message. The SQL constraint will always be specified in the field declaration section of the Python code; it is defined before entering the coding section.
Python Constraints:
Data consistency can be efficiently ensured using SQL constraints. To ensure data consistency in other situations, we might need to utilize more complicated tests. In such situations, we can use python constraints.
The Python constraint is a method decorated with constraints(). It is included on a record of the set. Decorators are used to specifying which fields are involved in the constraints. When a field is changed, these constraints are automatically considered.
For example,
@api.constrains('product_id')
def check_product_is_not_kit(self):
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 can not have a reordering rule."))
It is the sql_constraints that were taken from the warehouse, and it checks to make sure that any reordering rules cannot apply to a product with a kit-type bill of materials. If it doesn't work, an error message appears.
Computed Fields
In any real-world business scenario, there are some fields with values derived from other fields. The calculated values are either from the same records or from records that are connected to them. We can take advantage of computed fields in this situation. We can use an Odoo function to calculate the values for the fields.
Computed fields in Odoo have the same definition as other ordinary fields in Odoo. The attribute calculate, which is used to give the name of the function used for its computation, is the only exception. At runtime, computed fields are dynamically calculated. Unless you add such support directly by yourself, they are not searchable or writable.
A computed field has the same attribute ‘compute’ as other normal fields when it is declared. The name of the function as a string or as a function is the value for the compute attribute.
Dependencies:
Usually, the values of other fields affect the values of computed fields. Therefore, using the decorator depends(), we can define those requirements on the compute method. At runtime, the computation function performs dynamic calculations. We must avoid from recalculating the value inefficiently.
Hence, it needs to be aware of the other fields that depend on it and must include those fields in the decorator depends ().
For example,
amount = fields.Float('Amount')
total = fields.Float('Total', compute="_compute_total")
@api.depends('amount')
def _compute_total(self):
for rec in self:
rec.total = 2.0 * rec.amount
Here, we can calculate the total by using the function _compute_total. By default, computed fields are not kept in the database. In order to store the record in the database, we can use the store property.
We can use the store = True flag
For example,
total = fields.Float('Total', compute="_compute_total", store=True)
So, rather than having to be recomputed at runtime, they may be obtained just like any other regular fields. The ORM will be able to determine when these stored values need to be updated and recalculated.
Inverse Function
We know that by default, the computed fields are read-only. Which means the user couldn’t set the value for the computed fields. In some cases, it might be useful to still be able to set a value directly. For that, Odoo provides the ability to use an inverse function:
total = fields.Float(compute="_compute_total", inverse="_inverse_total")
amount = fields.Float()
@api.depends("amount")
def _compute_total(self):
for record in self:
record.total = 2.0 * record.amount
def _inverse_total(self):
for record in self:
record.amount = record.total / 2.0
The field is set by a compute method, and the field's dependencies are generated by an inverse method. When saving a record, the inverse method is called, and the compute method is activated whenever one of its dependencies changes.
Related Fields
We need to demonstrate the benefit of a field from a relational model to the current model in any real-world business scenario where we have some fields. In such a situation, we can use related fields. To do that, we need to specify the attribute related.
For example, we have a Many2one field partner_id, and its comodel is res.partners, and a related field is partner_name:
partner_id = fields.Many2one('res.partners')
partner_name = fields.Char('Name', related="partner_id.name")
Related fields are read-only and not copied by default, nor are they stored in the database. Using the store = True attributes, we can store the field record in the database.
partner_name = fields.Char('Name', related="partner_id.name", store=True)
Reference Fields
We have different types of relational models, many2one, one2many, and many2many. In relational models, we can create relationships between different models. A reference field helps us to create dynamic relationships between models. Relational models only permit the relationship between the two models by defining a related comodel in advance. However, if we utilize the reference field type, the user can select a related model from a list of options and a related record from that model.
We must first choose the target model before choosing the record in the reference field. For instance, let's say we need a field reference. We may sometimes need to choose a manufacturing order, sale order, or purchase order as a reference. We can use Reference fields in this situation.
reference_field = field.Reference(selection='', string='field name')
For this, we use the selection parameter. We must assign the model and its name to this parameter.
For example,
reference = fields.Reference(selection="[('sale.order', 'Sale Order'), ('purchase.order', ' Purchase Order')]", string="Reference")
Here, we can choose a Sale Order or Purchase Order, from the same field. Also possible to selection from a function.
reference = field.Reference(selection="get_model")
@api.model
def get_model(self):
models = self.env['ir.model'].search([])
return [(model.model, model.name), for model in models]
Returned values must be a list
Abstract Model
Main super-class for regular database-persisted Odoo models.
All Odoo models are created by inheriting from this class:
from . import models, fields
class AbstractModel(models.AbstractModel):
_name = "model name"
Field = fields.Char(string="field label")
Transient model
Model super-class for transient records, intended to be only temporarily persistent, that are regularly vacuum-cleaned.
A temporary paradigm has made it easier to manage access rights. New records can be made by any user at any time. They can only access the records they have created, though. The superuser has unrestricted access to all Transient Model records
from . import models, fields
class TransientModel(models.Transientmodel):
_name = "model name"
Field = fields.Char(string="field label")