Odoo is an open-source ERP and business application platform that offers a flexible framework for customization and extension.
In this blog, we'll explore monkey patching, a powerful technique that allows developers to customize and extend Odoo's functionality at runtime.
What is meant by monkey patching?
Monkey patching is a technique that allows you to modify or extend the functionality of a method or class at runtime, without changing the original source code. In Odoo, it means you can replace the default behavior with your own custom logic. Once patched, every time that method is called, Odoo will execute your version instead of the original.
Monkey patching in Odoo is usually applied in a structured and controlled way.
# Original class
class MyClass:
def original_method(self):
print("This is the original method")
# Monkey patching the class with a new method
def new_method(self):
print("This is the new method")
MyClass.original_method = new_method
By assigning new_method to MyClass.original_method, the original implementation is overridden. As a result, every time the original method is called, the new method is executed instead. This approach enables changes in behavior without modifying the original source code.
Example: Splitting Deliveries in Sales Orders
Imagine a sales order with multiple products. By default, Odoo creates a single delivery order for all items. However, in some cases, you may want to ship each product separately. To enable this, a field can be added to each sales order line to specify the delivery date for individual products, allowing more precise control over scheduling and logistics.
Inheriting the Sale Order Model
To enable separate deliveries based on delivery dates, a new field must be added to the sale.order.line to capture the specific delivery date for each item.
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
delivery_date = fields.Date("Delivery Date")
Include the field in the views
<odoo>
<record id="view_order_form" model="ir.ui.view">
<field name="name">sale.order.inherit.monkey.patching</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='order_line']/list/field[@name='price_unit']"
position="before">
<field name="delivery_date"/>
</xpath>
</field>
</record>
</odoo>
Updating the _prepare_procurement_values method
Monkey patching is applied to the _prepare_procurement_values method in the sale.order.line model.
def prepare_procurement_values(self, group_id=False):
Order_id = self.order_id
deadline_date = self.delivery_date or (
Order_id.date_order +
timedelta(days=self.customer_lead or 0.0)
)
planned_date = deadline_date - timedelta(
days=self.order_id.company_id.security_lead)
values = {
'group_id': group_id,
'sale_line_id': self.id,
'date_planned': planned_date,
'date_deadline': deadline_date,
'route_ids': self.route_ids,
'warehouse_id': Order_id.warehouse_id or False,
'product_description_variants': self.with_context(
lang=Order_id.partner_id.lang).
_get_sale_order_line_multiline_description_variants(),
'company_id': Order_id.company_id,
'sequence': self.sequence,
}
return values
After defining the new method, it is assigned to the SaleOrderLine class by replacing the original method, so Odoo will use the customized version instead:
SaleOrderLineOriginal._prepare_procurement_values = prepare_procurement_values
Inherit Stock Move Model
For this example, we need to apply monkey patching to two methods: _assign_picking and _key_assign_picking, so that the logic for splitting deliveries and aligning them with individual order lines is correctly applied throughout the sales and stock workflow. The _key_assign_picking method ensures that each sale order line gets a unique picking if the order contains multiple products.
Updating the _key_assign_picking method
def _key_assign_picking(self):
"""Each sale order line gets a unique picking if the order has multiple products"""
self.ensure_one()
if self.sale_line_id and self.sale_line_id.order_id.order_line:
order_lines = self.sale_line_id.order_id.order_line
if len(order_lines) > 1:
return (self.sale_line_id.id,)
return (self.picking_type_id.id, self.location_id.id, self.location_dest_id.id)
Modifying the _assign_picking method
def _assign_picking(self):
StockPicking = self.env['stock.picking']
# Sort moves by key to ensure proper grouping
sorted_moves = sorted(self, key=lambda m: m._key_assign_picking())
grouped_moves = groupby(sorted_moves, key=lambda m: m._key_assign_picking())
for group, moves in grouped_moves:
moves = self.env["stock.move"].concat(*moves)
moves = moves.filtered(lambda m: float_compare(
m.product_uom_qty, 0.0, precision_rounding=m.product_uom.rounding
) > 0)
if not moves:
continue
pick_values = moves._get_new_picking_values()
picking = StockPicking.create(pick_values)
moves.write({'picking_id': picking.id})
for move in moves:
move._assign_picking_post_process(new=True)
return True
In conclusion, the newly created functions are applied to the _assign_picking and _key_assign_picking methods of the StockMove class like
StockMoveOriginal._assign_picking = _assign_picking
StockMoveOriginal._key_assign_picking = _key_assign_picking
Monkey patching in Odoo provides a powerful way to customize default behavior without modifying the core code. This approach demonstrates how runtime modifications can enhance Odoo’s flexibility and meet specific business requirements.
To read more about What is Monkey Patching & How It Can Be Applied in Odoo 18, refer to our blog What is Monkey Patching & How It Can Be Applied in Odoo 18.