Inheritance in Odoo: the feature that decides whether your code survives upgrades
Classical extension, delegation and view inheritance. The three ways to modify Odoo without touching the core —and why inheriting instead of copying saves you every migration.
Part of our complete guide: Odoo module development.
If I had to pick a single idea to explain why some Odoo systems age well and others turn into a nightmare, it would be this: inheritance. It's the mechanism that lets you add to, modify and extend almost anything in Odoo —a core model, a standard view, a business method— without touching the original code. And "without touching the original" isn't an aesthetic preference: it's the only thing that lets your work survive when Odoo ships the next version.
The catch is that "inheritance" in Odoo means three different things, and mixing them up is a constant source of confusion. Let's pull them apart.
1. Classical extension: adding to something that already exists
This is the one you'll use 90% of the time. You declare _inherit with the name of an existing model and, without creating a new table, you add fields, methods or override its behaviour.
from odoo import models, fields
class SaleOrder(models.Model):
_inherit = "sale.order"
delivery_zone = fields.Char(string="Delivery zone")
That alone adds a column to sale.order and makes it available across the whole system. The interesting part comes when you want to step into the logic, for example when confirming an order:
def action_confirm(self):
# your logic before...
res = super().action_confirm()
# your logic after...
for order in self:
order.message_post(body="Order confirmed and zone assigned.")
return res
That super() is sacred. It's what calls Odoo's original method (and any other module that also extended it). Forgetting it —or not returning its result— is one of the subtlest, most maddening bugs there is: suddenly "something you didn't touch stops working". You touched the flow without realising.
2. Delegation: "is-a" without copying
The second form is less common but brilliant when it fits. With _inherits (note the s at the end) your model delegates to another: under the hood it stores a link to the parent model and exposes all its fields as if they were its own. The canonical example is res.users, which delegates to res.partner:
class Member(models.Model):
_name = "club.member"
_inherits = {"res.partner": "partner_id"}
partner_id = fields.Many2one("res.partner", required=True, ondelete="cascade")
membership_number = fields.Char()
Now a member "has" a name, email and phone without you declaring them, because it lives on top of a contact. The difference from classical extension is key: with _inherit you modify the existing model; with _inherits you create a new model that reuses another by composition. Confusing _inherit and _inherits is a classic; always check it twice.
3. View inheritance: changing the UI without rewriting it
The third one doesn't touch Python: it touches XML. It's the one most often done wrong. Instead of copying the core's entire form and editing it (suicide when it comes to upgrades), you point with surgical precision at the spot where you want to insert your field, using xpath and position.
<record id="view_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.form.delivery</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='payment_term_id']" position="after">
<field name="delivery_zone"/>
</xpath>
</field>
</record>
"After the payment_term_id field, put mine." That's it. When Odoo updates that form in the next version, your addition is reapplied on top, intact. The one who copied the whole view, by contrast, is stuck with a frozen version of the form from two years ago and loses everything new. We see it in every migration: the module that "inherited well" goes up in hours; the one that "copy-pasted" takes days.
The rule we repeat endlessly: inherit, don't copy
It all boils down to a principle we already mentioned in the module best practices: duplicating core logic is technical debt with an expiry date. The date is the day of the next upgrade. Inheriting takes a bit more thought up front and pays for itself on every version jump.
My practical advice, after quite a few migrations: before writing anything, ask yourself "does this already exist in Odoo in some form?". Almost always the answer is yes, and almost always the right move is to extend it, not reinvent it. If you understand this and the super() thing, you avoid most of the problems that reach us in audits.
Why this matters even if you don't code
If you're the decision-maker, inheritance explains a phrase you'll hear a lot: "this is customisable, but it has to be done right". A build that inherits is an asset that evolves with you; one that copies and patches is a time bomb that goes off on the next version and ties you to whoever wrote it. Knowing how to tell one from the other —or having someone who does— is what separates an Odoo that grows from one that stalls.
Got custom modules and worried about how they'll hold up on the next upgrade? In a code audit we check whether they inherit as they should or whether a surprise awaits. And if you're building from scratch, this is how we do it in our custom development service. For the foundations, start with the anatomy of a module and relations between models.
Comments (0)
Be the first to comment.
Sign in to leave a comment.
Sign inComments are reviewed before publishing.
Related articles
Automated actions in Odoo: let the ERP do the boring work
Server actions, automation rules and scheduled jobs: the three pieces that make Odoo react, notify and run on its own —without anyone having to remember.
PDF reports with QWeb in Odoo: from the form to the document that closes deals
QWeb is HTML that turns into PDF. We show you how to build a report, loop over records with t-foreach, and why t-field saves you a thousand headaches.
Relations between models in Odoo: Many2one, One2many and Many2many without the confusion
The three relations that connect your data, what each one really stores in the database, and the commands to write them without breaking anything.