Custom exception handlers in Odoo client

Odoo backend web client has a crash manager service, which is used for handling both RPC errors and errors which occur in the JavaScript code. The crash manager differentiates between RPC exceptions based on their type and class name, so that some errors, eg. the ORM errors (ValidationError, UserError...) are displayed as warnings (no traceback) and others as errors with a traceback (indicating the error was not handled in the action method, so it is probably unexpected and the traceback might be helpful).

For RPC errors, the crash manager also peeks into the crash registry for a suitable exception handler based on the full exception class name, and, if one is found, delegates the displaying of the exception to that handler. That means that we can add exception handlers with custom behavior for our custom exceptions - that's neat!

This article is written based on Odoo 11.0 Community version.

There is one custom exception handler in Odoo core - the RedirectWarningHandler for handling the RedirectWarning exception, which allows you to display a warning dialog with a button that will redirect the user to some action, for example:

from odoo import _, api, exceptions, models


class MyModel(models.Model):
    _name = 'my.model'

    @api.multi
    def action_foo(self):
        if not some_condition:
            raise exceptions.RedirectWarning(
                _('foo is not configured!'),
                self.env.ref('my_addon.action_config_foo').id,
                _('Go to configuration'),
            )
        # Do something else.

Which will look something like this: Redirect warning dialog

Writing a custom exception handler

Adding your own custom exception handler to Odoo web client is really easy - let's try it!

For the sake of this tutorial, let's assume we are creating a module with the technical name custom_addon.

First, our custom module will have to depend on at least the web module.

Next, let's define our custom exception class. I recommend declaring custom exceptions in a Python module called exceptions.py in addon's main directory, which makes it easy to find and import from other Odoo addons. Our exception might look something like this:

class BSOD(Exception):
    '''For cases when a regular exception just doesn't cut it.'''

Our custom exception's full class name (including the module name) will be: odoo.addons.custom_addon.exceptions.BSOD - this will come in handy later.

Now, let's create an exception handler - create a JavaScript file in the addon's static/src/js/ directory and make sure it is loaded along with other backend assets. To do that, extend the web.assets_backend template, eg.:

<template id="assets_backend" name="Custom backend assets" inherit_id="web.assets_backend">
    <xpath expr="." position="inside">
        <script
            type="text/javascript"
            src="/custom_addon/static/src/js/exception_handler.js"></script>
    </xpath>
</template>

Now, let's define our custom exception handler in exception_handler.js:

odoo.define('custom_addon.exception_handler', function (require) {
    'use strict';

    var core = require('web.core');

    var BSODHandler = core.Class.extend({
        init: function(parent, error) {
            this.error = error;
        },
        display: function() {
            // Display the Blue Screen of Death.
        }
    });

    // Register our handler as capable of handling the BSOD exception.
    core.crash_registry.add('odoo.addons.custom_addon.exceptions.BSOD', BSODHandler);

    return BSODHandler;
});

The crucial part is registering our custom exception handler in the crash_registry - here we need to use the full class name of our exception class (including the module name), which I've mentioned earlier. As for the exception handler - it should have an init method taking an error and a display method, which should do everything which is needed to inform the user about the exception, eg. show a pop-up dialog (Odoo defines the ExceptionHandler interface, but, unfortunately, it is not exported, so we simply duck-type it by extending the core.Class).

I didn't include the code for displaying the Blue Screen of Death here in order to keep the example short, but if you'd like to see how I did it you can find the full code here.

Now, we can raise our custom exception somewhere in the code and cringe at the Blue Screen of Death:

from odoo import _, api, models
from odoo.addons.custom_addon import exceptions


class ResUsers(models.Model):
    _inherit = 'res.users'

    @api.multi
    def unlink(self):
        if self.env.user.id in self.ids:
            raise exceptions.BSOD(_('Does not compute'))
        return super().unlink()

The final result: Blue Screen of Death exception handler