Back to all engineering articles
Sep 25, 20267 min readOdoo & ERP Architecture

Mastering Odoo 18/19 Model Inheritance, Wizards, and Custom Business Flows

A deep architectural guide into extending Odoo ORM models, writing safe model inheritances, building multi-step interactive wizards, and enforcing strict record rules.

#Odoo 18#Python ORM#Wizards#PostgreSQL#Enterprise ERP

Architecture of Odoo 18/19 Model Inheritance

In enterprise ERP implementations, maintaining clean boundaries between standard core modules and client customizations is essential. Odoo provides three distinct inheritance patterns:

1. Traditional In-place Extension (`_inherit = 'res.partner'`) When you want to extend existing DocTypes/models by adding computed fields, constraints, or overriding core CRUD methods:

class ResPartner(models.Model):
    _inherit = 'res.partner'

vehicle_ids = fields.One2many('garage.vehicle', 'partner_id', string='Registered Vehicles') total_service_invoices = fields.Monetary( compute='_compute_service_totals', string='Total Garage Spend' ) @api.depends('vehicle_ids.repair_order_ids.invoice_ids.state') def _compute_service_totals(self): for partner in self: # High performance aggregated query total = sum(order.amount_total for v in partner.vehicle_ids for order in v.repair_order_ids) partner.total_service_invoices = total ```

2. Prototype Inheritance (`_name` and `_inherit`) Used when creating an independent table that copies fields and behaviors from an existing model without modifying the parent.

3. Delegation Inheritance (`_inherits = {'res.partner': 'partner_id'}`) Used for seamless composition, such as linking clinic patients or automotive customers to the underlying contact directory.

---

Building Multi-Step Wizards for Complex Actions

Wizards in Odoo are transient models (`models.TransientModel`) that store temporary state in memory before committing changes to the primary database.

class QuickPrescriptionWizard(models.TransientModel):
    _name = 'clinic.prescription.wizard'
    _description = 'Quick Prescription and Medicine Dispense'

patient_id = fields.Many2one('clinic.patient', string='Patient', required=True) examination_date = fields.Date(default=fields.Date.context_today) line_ids = fields.One2many('clinic.prescription.wizard.line', 'wizard_id', string='Medicines')

def action_confirm_and_dispense(self): self.ensure_one() # Create Sales Order & Reserve Pharmacy Stock sale_order = self.env['sale.order'].create({ 'partner_id': self.patient_id.partner_id.id, 'origin': f'Prescription - {self.patient_id.name}', 'order_line': [ (0, 0, { 'product_id': line.medicine_id.id, 'product_uom_qty': line.quantity, 'price_unit': line.medicine_id.list_price, }) for line in self.line_ids ] }) return sale_order.action_view_details() ```

Best Practices for Enterprise Maintainability

  • **Avoid Storing Computations Unnecessarily**: Only add `store=True` when the field is actively indexed for searching or grouping.
  • **Batch ORM Operations**: Always prefer `self.env['model'].create([...])` with lists of dictionaries instead of looping `create()` calls.
  • **Use Explicit `@api.constrains` and SQL Constraints**: Prevent dirty data at the database level for mission-critical accounting and stock records.
Võ Nghĩa Kỳ

Võ Nghĩa Kỳ

Senior Full-Stack & Odoo 18/19 Developer

Discuss Project Architecture