Skip to content
Back to blog
DevelopmentPythonArchitecture

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.

COConsultor Odoo30 May 20264 min read

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.

#Development#Python#Architecture
Share article

Comments (0)

Be the first to comment.

Sign in to leave a comment.

Sign in

Comments are reviewed before publishing.

Ready to get the most out of Odoo?

Tell us your challenge. In a first 30-minute call we'll tell you how Odoo can help, no strings attached.