Contributors mailing list archives

Re: How to write hook addon properly.

Ecosoft, Kitti Upariphutthiphong
- 08/03/2019 01:33:50
Richard, thank you so much for taking your time explaining this to me. Much appreciated :)


On Fri, Mar 8, 2019 at 5:45 AM Richard deMeester <> wrote:
My way of explaining this:

All of the python code is "loaded and available" and the classes are instantiated, whether the module is installed or not - Odoo will control, by knowledge of the modules installed, which methods are to be called and in which order.

Lowest level: -

Level 1
       super(level1, self).invoice_create()

We can "replace" invoice_create at level 0 by creating a class

New module:

lowest_level.invoice_create = new_module.invoice_create

Problem: Whether the new module is installed or not, Odoo knows that "Lowest level" is installed and should be called, and it will then execute our code in "New module", EVEN IF NEW MODULE IS NOT INSTALLED.
Solution: New module needs to somehow be aware if it is installed or not, and if not, it should not try to execute, but should instead call the original lowest level.

We use this method to refactor Odoo methods which are either very long and need some hooks, or very low and we need to respect the inheritance (as Jordi mentioned)

In this example, we override the method completely, and it is always executed at the original level of inheritance, but our hooks will only be called if the module is installed....

We can then even extend the hooks in other modules that depend on this, and always are confident of correct inheritance in both the original method and our new hooks.


from odoo.addons.mrp.wizard.mrp_product_produce import MrpProductProduce as OriginalMrpProductProduce

class MrpProductProduce(models.TransientModel):
    _inherit = "mrp.product.produce"

    Code taken from mrp/wizard/
    We make the original point to this, and then make sure the super is calling the correct super!

    def default_get_refactored(self, fields):

        # Modified
        res = super(OriginalMrpProductProduce, self).default_get(fields)

        if self._context and self._context.get('active_id'):
            production = self.env['mrp.production'].browse(self._context['active_id'])
            serial_finished = (production.product_id.tracking == 'serial')
            if serial_finished:
                todo_quantity = 1.0
                main_product_moves = production.move_finished_ids.filtered(lambda x: ==
                todo_quantity = production.product_qty - sum(main_product_moves.mapped('quantity_done'))
                todo_quantity = todo_quantity if (todo_quantity > 0) else 0
            if 'production_id' in fields:
                res['production_id'] =
            if 'product_id' in fields:
                res['product_id'] =
            if 'product_uom_id' in fields:
                res['product_uom_id'] =
            if 'serial' in fields:
                res['serial'] = bool(serial_finished)
            if 'product_qty' in fields:
                res['product_qty'] = todo_quantity
            if 'produce_line_ids' in fields:
                lines = []
                for move in production.move_raw_ids.filtered(lambda x: (x.product_id.tracking != 'none') and x.state not in ('done', 'cancel') and x.bom_line_id):
                    qty_to_consume = float_round(todo_quantity / move.bom_line_id.bom_id.product_qty * move.bom_line_id.product_qty,
                                                 precision_rounding=move.product_uom.rounding, rounding_method="UP")
                    for move_line in move.move_line_ids:
                        if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) <= 0:
                        if move_line.lot_produced_id or float_compare(move_line.product_uom_qty, move_line.qty_done, precision_rounding=move.product_uom.rounding) <= 0:
                        to_consume_in_line = min(qty_to_consume, move_line.product_uom_qty)
                            'qty_to_consume': to_consume_in_line,
                            'qty_done': 0.0,

                        # Inserted
                        # THIS module might not be installed - code is still run as python class has been instantiated.
                        if hasattr(self, 'default_get_additional_line_vals'):
                            lines[-1].update(self.default_get_additional_line_vals(fields, lines[-1], production, move, move_line))

                        qty_to_consume -= to_consume_in_line
                    if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0:
                        if move.product_id.tracking == 'serial':
                            while float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0:
                                    'qty_to_consume': 1,
                                    'qty_done': 0.0,

                                # Inserted
                                if hasattr(self, 'default_get_additional_line_vals'):
                                    lines[-1].update(self.default_get_additional_line_vals(fields, lines[-1], production, move, None))

                                qty_to_consume -= 1
                                'qty_to_consume': qty_to_consume,
                                'qty_done': 0.0,

                            # Inserted
                            if hasattr(self, 'default_get_additional_line_vals'):
                                lines[-1].update(self.default_get_additional_line_vals(fields, lines[-1], production, move, None))

                res['produce_line_ids'] = [(0, 0, x) for x in lines]

            # Inserted
            if hasattr(self, 'default_get_additional_vals'):
                res.update(self.default_get_additional_vals(fields, res, production))

        return res

    def default_get_additional_vals(self, fields, vals_so_far, production):
        return {}

    def default_get_additional_line_vals(self, fields, vals_so_far, production, move, move_line):
        return {}

# Make original now this version.
OriginalMrpProductProduce.default_get = MrpProductProduce.default_get_refactored

Richard deMeester

Senior Development Analyst

WilldooIT Pty Ltd


M: +61 403 76 76 76

P: +61 3 9135 1900

A: 10/435 Williamstown Road, Port Melbourne, Vic 3207



Our vision is to provide end to end IT solutions to help our clients achieve their desired objectives and provide growth opportunities for everyone working in the company to reach their full professional potential.



DISCLAIMER | This electronic message together with any attachments is confidential. If you are not the recipient, do not copy, disclose, or use the contents in any way. Please also advise us by e-mail that you have received this message in error and then please destroy this email and any of its attachments. WilldooIT Pty. Ltd. is not responsible for any changes made to this message and/or any attachments after sending by WilldooIT Pty. Ltd. WilldooIT Pty. Ltd. use virus scanning software but exclude all liability for virus or anything similar in this email or attachment.

From: Kitti Upariphutthiphong <>
Sent: Thursday, 7 March 2019 2:52 PM
To: Contributors
Subject: How to write hook addon properly.
Dear all

I am looking for the right way to write a hook addon when working with OCA modules.

I see that, it is doing things like this in

def post_load_hook():
def new_action_invoice_create(self, grouped=False, final=False):

if not hasattr(self, '_get_invoice_group_key'):
return self.action_invoice_create_original(grouped=grouped,

From what I see, since this module is being installed, the new_action_invoice_create() will be used always anyway. I am trying to understand why we would need to do this, instead of directly overwrite method with hook point ?

Thank you for your support.
Kitti U.

Post to: