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.
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ỳ
Senior Full-Stack & Odoo 18/19 Developer