Skip to content

Odoo 19 Inheritance Patterns: Classical, Prototype & Delegation

DeployMonkey Team · March 22, 2026 14 min read

Three Types of Inheritance

Odoo has three distinct inheritance mechanisms, and confusing them is one of the most common mistakes for new developers.

TypeDeclarationNew Table?Use Case
Classical_inherit = 'model'NoExtend existing model (add fields, override methods)
Prototype_inherit = 'model' + _name = 'new.model'YesCopy model structure to a new model
Delegation_inherits = {'model': 'field_id'}YesCompose models (like has-a relationship)

Classical Inheritance (Most Common)

Extends an existing model without creating a new database table. Used in 95% of customizations.

# Add a field to res.partner
class ResPartner(models.Model):
    _inherit = 'res.partner'

    credit_score = fields.Integer(string='Credit Score')
    loyalty_tier = fields.Selection([
        ('bronze', 'Bronze'),
        ('silver', 'Silver'),
        ('gold', 'Gold'),
    ], string='Loyalty Tier', default='bronze')

    def action_check_credit(self):
        """New method added to res.partner."""
        for partner in self:
            # Custom logic
            pass

Overriding Methods

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    def action_confirm(self):
        """Override confirm to add custom logic."""
        # Call original method first
        result = super().action_confirm()
        # Add custom logic after
        for order in self:
            if order.amount_total > 10000:
                order.message_post(
                    body="Large order confirmed: %s" % order.amount_total
                )
        return result

Always call super() unless you intentionally want to replace the entire method.

Prototype Inheritance (Rare)

Creates a new model that copies the fields and methods of an existing model into a new database table.

# Create a new model based on res.partner structure
class Vendor(models.Model):
    _name = 'my.vendor'
    _inherit = 'res.partner'  # Copy structure
    _description = 'Vendor'

    # This creates a NEW table 'my_vendor' with all res.partner fields
    # Plus any new fields you add:
    vendor_rating = fields.Float(string='Rating')
    payment_terms = fields.Char(string='Payment Terms')

Use sparingly. Prototype inheritance duplicates data and does not maintain a link to the original model. In most cases, classical inheritance with a type field is better.

Delegation Inheritance

Composes models using a foreign key. The child model has access to all parent fields through the link field. Think of it as a "has-a" relationship.

class Employee(models.Model):
    _name = 'hr.employee'
    _inherits = {'res.partner': 'address_home_id'}
    _description = 'Employee'

    # address_home_id is the link to res.partner
    address_home_id = fields.Many2one(
        'res.partner', string='Private Address',
        required=True, ondelete='restrict',
    )

    # Employee-specific fields
    department_id = fields.Many2one('hr.department')
    job_title = fields.Char()

    # Can access partner fields directly:
    # employee.name → reads from res.partner
    # employee.email → reads from res.partner

Delegation creates a new table but links to the parent via a foreign key. Reading partner fields on the employee transparently reads from the partner record.

View Inheritance

<!-- Extend an existing view -->
<record id="view_partner_form_inherit" model="ir.ui.view">
    <field name="name">res.partner.form.inherit</field>
    <field name="model">res.partner</field>
    <field name="inherit_id" ref="base.view_partner_form"/>
    <field name="arch" type="xml">
        <xpath expr="//field[@name='phone']" position="after">
            <field name="credit_score"/>
            <field name="loyalty_tier"/>
        </xpath>
    </field>
</record>

Mixin Pattern

Abstract models used as mixins provide reusable functionality:

class TrackingMixin(models.AbstractModel):
    _name = 'tracking.mixin'
    _description = 'Tracking Mixin'

    created_by = fields.Many2one('res.users', default=lambda self: self.env.user)
    last_modified_by = fields.Many2one('res.users')

    def write(self, vals):
        vals['last_modified_by'] = self.env.user.id
        return super().write(vals)

# Use the mixin
class Equipment(models.Model):
    _name = 'equipment.item'
    _inherit = ['mail.thread', 'tracking.mixin']  # Multiple inheritance
    _description = 'Equipment'

    name = fields.Char(required=True)

Common Mistakes

  • Forgetting super() in method overrides — breaks the original behavior
  • Using prototype inheritance when classical is needed — creates unnecessary data duplication
  • Not specifying _name for prototype — accidentally extends the original model instead of creating a new one
  • Circular inheritance — Model A inherits B which inherits A
  • Overriding create/write without super() — breaks ORM functionality

Getting Started

Use Claude Code to generate inheritance code — it understands when to use classical vs prototype vs delegation based on your requirements. Deploy and test on DeployMonkey.