Singleton error is one of the most common errors in Odoo faced by many developers. Singleton can be defined as the recordset with only one record. Singleton error usually occurs when a method requires a single record to invoke, but it is called by multiple records or a recordset instead. Then the method will not be able to recognize for which record it should process and raises the expected singleton error.
class SaleOrderSingleton(models.Model):
_inherit = 'sale.order'
@api.onchange('partner_id')
def _onchange_partner_id(self):
if self.order_line:
print(self.order_line.id)
Output:
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/cybrosys/odoo15/odoo/http.py", line 643, in _handle_exception
return super(JsonRequest, self)._handle_exception(exception)
File "/home/cybrosys/odoo15/odoo/http.py", line 301, in _handle_exception
raise exception.with_traceback(None) from new_cause
ValueError: Expected singleton: sale.order.line(<NewId origin=238>, <NewId origin=239>, <NewId origin=240>)
Here, the singleton error is raised since there is more than one orderline created for the sale order, and the print function cannot identify for which recordset it needs to process.
There are many ways to handle singleton errors in Odoo, and the two common methods are
1. Iterate over the recordset
2. Using ensure_one()
1. Iterate over the recordset.
Whenever a method needs to process a recordset instead of a record, the best way to avoid the singleton error is to iterate over the recordset.
In the above example, we can simply iterate through the recordset and get the output without getting a singleton error.
Class SaleOrderSingleton(models.Model):
_inherit = 'sale.order
@api.onchange('partner_id')
def _onchange_partner_id(self):
if self.order_line:
for rec in self.order_line:
print(rec.id)
Output:
NewId_238
NewId_239
NewId_240
So, whenever a computed field is added to the list view it is required to iterate through the self in the compute function used or there will be singleton errors.
2. ensure_one()
def ensure_one(self):
"""Verify that the current recordset holds a single record.
:raise odoo.exceptions.ValueError: ``len(self) != 1``
"""
try:
# unpack to ensure there is only one value is faster than len when true and
# has a significant impact as this check is largely called
_id, = self._ids
return self
except ValueError:
raise ValueError("Expected singleton: %s" % self)
Ensure one is one of the common ORM methods in odoo, which can be used to handle singleton error in cases where only one record is expected to be passed on. It verifies that the current recordset holds a single record or it will throw an error either if the recordset is null or the length of recordset is more than one. The main purpose of using ensure_one() is to make sure the method will only start processing if the length of the recordset being passed on is one.
For example:
class SaleOrderSingleton(models.Model):
_inherit = 'sale.order'
@api.onchange('partner_id')
def _onchange_partner_id(self):
if self.order_line:
order_line_ids = self.order_line
order_line_ids.print_order_line_ids()
class SaleOrderLineSingleton(models.Model):
_inherit = 'sale.order.line'
def print_order_line_ids(self):
print(self.id)
Output:
ValueError: Expected singleton: sale.order.line(<NewId origin=238>, <NewId origin=239>, <NewId origin=240>)
We can use ensure_one() method to stop the method if the self has more than one record or if it contains a null recordset. Here the program will be stopped before calling the print function and thus ensures the method only works if the condition is true.
class SaleOrderLineSingleton(models.Model):
_inherit = 'sale.order.line'
def print_order_line_ids(self):
self.ensure_one()
print(self.id)
There are also many other ways to handle the singleton errors regarding the logic of the code.
For example, In some cases when we use a search method to fetch records inside a function and there occurs a singleton error. Here we can use different domains for the search method to filter the records or use limit =1 inside the search method based on the logic of the code. Likewise, we could use different logic to avoid singleton errors.