Handling errors is an important part of coding. We know that the Odoo Exceptions module gives us some default exception types like UserError, ValidationError, CacheMiss, AccessError, and so on. We use them or create new exceptions to raise exceptions in our code. While raising, We can handle these exceptions with Try and Except statements.
Raising Exceptions
We can raise exceptions based on any conditions in the code.
Here is an example code for raising an exception:
from odoo import models, _
from odoo.exceptions import UserError
class SaleOrder(models.Model):
_inherit = 'sale.order'
def action_confirm(self):
""" Raise an error on confirming the Sale Order if Tax = 0"""
if self.amount_tax == 0:
raise UserError(
_("You cannot confirm the Sale Order without Tax."))
return super(SaleOrder, self).action_confirm()
Here, an error message will be displayed in a pop-up.
For more examples, visit Raising Exceptions in the Odoo 15.
There are other cases where we will get errors from code. For example division of a number with zero:
from odoo import fields, models
class SaleOrder(models.Model):
_inherit = 'sale.order'
test_tax = fields.Char(string='Test Tax')
def action_confirm(self):
""" Update Test Tax on confirming the Sale Order """
if self.amount_total/self.amount_tax:
self.test_tax = 'Success'
else:
self.test_tax = 'Failed'
return super(SaleOrder, self).action_confirm()
This code will give an error if the amount_tax is zero.
The ZeroDivisionError is a built-in exception in python.
Built-in Exceptions
There are predefined built-in exceptions in python that give an error on the occurrence. Odoo will show these exceptions as server errors. Some built-in exceptions are listed below:
Exception | Description |
Exception | Base class for all exceptions |
ZeroDivisionError | Raised when the second operator in a division is zero |
SyntaxError | Raised when a syntax error occurs |
NameError | Raised when a variable does not exist |
KeyError | Raised when a key does not exist in a dictionary |
ImportError | Raised when an imported module does not exist (or could not be imported) |
SystemError | Raised when a system error occurs |
ArithmeticError | Raised when an error occurs in numeric calculations |
EOFError | Raised when the input() method hits an "end of file" condition (EOF) |
RuntimeError | Raised when an error occurs that does not belong to any specific exceptions |
OSError | Raised when a system-related operation causes an error |
TabError | Raised when indentation consists of tabs or spaces (when mixing tabs and spaces for indentation) |
UnicodeError | Raised when a unicode problem occurs |
UnicodeTranslateError | Raised when a unicode translation problem occurs |
UnicodeDecodeError | Raised when a unicode decoding problem occurs |
UnicodeEncodeError | Raised when a unicode encoding problem occurs |
MemoryError | Raised when a program runs out of memory |
IndentationError | Raised when indentation is not correct (4 spaces are recommended for the indentation by the PEP8 style guide) |
FloatingPointError | Raised when a floating point calculation fails |
AssertionError | Raised when an assert statement fails (mostly used in python tests in Odoo) |
GeneratorExit | Raised when a generator is closed (with the close() method) |
StopIteration | Raised when the next() method of an iterator has no further values |
TypeError | Raised when two different types are combined |
KeyboardInterrupt | Raised when the user presses Ctrl+c, Ctrl+z, or Delete |
OverflowError | Raised when the result of a numeric calculation is too large |
SystemExit | Raised when the sys.exit() function is called |
NotImplementedError | Raised when an abstract method requires an inherited class to override the method |
UnboundLocalError | Raised when a local variable is referenced before assignment |
LookupError | Raised when errors raised can’t be found |
ReferenceError | Raised when a weak reference object does not exist |
AttributeError | Raised when attribute reference or assignment fails |
ValueError | Raised when there is a wrong value in a specified data type |
IndexError | Raised when an index of a sequence does not exist (mostly happens when an index is specified with an empty iterable object, empty recordset, or empty string) |
Try and Except Statement
As we have discussed above, the built-in python exceptions and the manually added exceptions will stop the execution and show the corresponding error to the user. Sometimes there will be requirements like storing the errors in the record of a model or doing any functions on the failure of another function instead of showing an error to the user.
In the second example, the field Test Tax is not updated, and the state of sale order is not changed due to the error.
So, we can do it with the try and except statement. I am adding one more field to show the error in the Sale Order(models/sale_order.py):
# -*- coding: utf-8 -*-
from odoo import fields, models
class SaleOrder(models.Model):
_inherit = 'sale.order'
test_tax = fields.Char(string='Test Tax')
capture_error = fields.Char(string='Error')
def action_confirm(self):
""" Update Test Tax and Error on confirming the Sale Order """
try:
if self.amount_total/self.amount_tax:
self.test_tax = 'Success'
except UserError as e:
self.capture_error = str(e)
self.test_tax = 'Failed'
except ZeroDivisionError as e:
self.capture_error = str(e)
self.test_tax = 'Failed'
except Exception as e:
self.capture_error = str(e)
self.test_tax = 'Failed'
else:
self.capture_error = 'No errors'
finally:
self.capture_error = 'Finally: ' + self.capture_error
return super(SaleOrder, self).action_confirm()
The XML code to add these fields in the form view of Sale Order(views/sale_order_views.xml):
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="sale_order_view_form" model="ir.ui.view">
<field name="name">sale.order.view.form.inherit.handling_errors</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='payment_term_id']" position="after">
<field name="test_tax" readonly="1" />
<field name="capture_error" readonly="1" />
</xpath>
</field>
</record>
</odoo>
In the python code, a try and except statement is included. The try and except statement consists of a try block followed by one or more except block(s) and optional else and finally blocks.
We add the code for which we have to catch an error in the try block. Then we will add the except blocks to catch errors. If we are looking for any specific exceptions, we can add them. For example, UserError, ZeroDivisionError, and Exception have been added to the code above. The first exception will trigger. Since we have the error, float division by zero in the case below, the except block for ZeroDivision error will be triggered, and the value ‘Failed’ will be stored in Test Tax and the error message in the Error. The blocks for UserError and Exception will be skipped.
If we have to catch all errors, then we will use the Exception.
Since it is the base class for exceptions, we can catch all the errors. If we are supposed to add multiple except blocks as we have here, the Exception should be added as the last one. Then we have the two optional blocks: else and finally. The else would trigger only if the code in the try block was executed successfully without an error. The value ‘Success’ will be stored in Test Tax and ‘No errors in the Error. At last, we have the final block. It will run in all cases. You can see that the ‘Finally: ‘ is added in both cases in the value stored in the Error field.
In the first two examples: the execution is stopped, and the error message is displayed. But, in the last example, the entire function is executed and values in the fields are updated from the function, and the state of the sale order is changed.
In Odoo, we can use urllib.error, urllib3.exceptions, and requests. exceptions to manage the exceptions related to the HTTP connection and requests. An example is given below:
import requests
from odoo import api
@api.model
def _fetch_data(self, base_url, params, content_type=False):
result = {'values': dict()}
try:
response = requests.get(base_url, timeout=3, params=params)
response.raise_for_status()
if content_type == 'json':
result['values'] = response.json()
elif content_type in ('image', 'pdf'):
result['values'] = base64.b64encode(response.content)
else:
result['values'] = response.content
except requests.exceptions.HTTPError as e:
result['error'] = e.response.content
except requests.exceptions.ConnectionError as e:
result['error'] = str(e)
return result
Catching and handling the errors are important since they help us to manage the flow of the occurrence of errors.