📋 What You Will Learn
- The three inheritance types and when to use each
- Classical inheritance: extending models with
_inherit - Prototype inheritance: forking a model with
_inherit + _name - Delegation inheritance: embedding with
_inherits - Extending methods safely with
super() - Useful mixins:
mail.threadandmail.activity.mixin
Three Types of Inheritance
| Type | How to declare | Creates new table? | Use case |
|---|---|---|---|
| Classical | _inherit = 'existing.model' | No — extends in place | Add fields/methods to any model from another module |
| Prototype | _inherit = 'existing.model' + _name = 'new.model' | Yes — new table, new model | Start from an existing model's structure for a related but distinct concept |
| Delegation | _inherits = {'existing.model': 'field_id'} | Yes — own table + FK | Embed an existing model's fields transparently (e.g. embed res.partner) |
Classical Inheritance (_inherit)
Classical inheritance is the most common pattern in Odoo. You write a new Python class with only _inherit (no _name), and Odoo merges it into the existing model at startup. The changes apply globally — every module that uses the model sees the new fields and methods.
from odoo import models, fields, api
# Extending res.partner — adds library-specific fields to the contact model
class ResPartner(models.Model):
_inherit = 'res.partner' # no _name — extends in place
# These fields are added to the existing res.partner table
is_library_member = fields.Boolean(string='Library Member', default=False)
library_card_number = fields.Char(string='Library Card Number')
membership_expiry = fields.Date(string='Membership Expiry')
active_loan_count = fields.Integer(
string='Active Loans',
compute='_compute_active_loan_count',
)
@api.depends()
def _compute_active_loan_count(self):
for partner in self:
partner.active_loan_count = self.env['library.loan'].search_count([
('borrower_id', '=', partner.id),
('state', '=', 'active'),
])
There is no limit on how many modules can inherit the same model. All extensions are stacked — Odoo's registry merges them all into one final class at startup time using Python's MRO (method resolution order).
Prototype Inheritance (_inherit + _name)
Prototype inheritance copies the structure of an existing model to create a brand-new model with its own database table. You specify both _inherit (the source) and _name (the new model's technical name).
from odoo import models, fields
# Start from mail.thread + mail.activity.mixin structure
# Create a new, independent model for eBook loans
class LibraryEbookLoan(models.Model):
_name = 'library.ebook.loan' # new model — new table
_description = 'eBook Loan'
_inherit = 'library.loan' # copy fields and methods from library.loan
# Add eBook-specific fields on top of inherited ones
download_link = fields.Char(string='Download URL')
device_id = fields.Char(string='Device ID')
drm_token = fields.Char(string='DRM Token')
Changes made to library.loan after the prototype was created do NOT automatically propagate to library.ebook.loan. Use prototype inheritance sparingly — only when you genuinely need a separate model. Most of the time, classical inheritance with an extra field to distinguish the subtype is a better approach.
Delegation Inheritance (_inherits)
Delegation inheritance is the most sophisticated type. Your model has a Many2one field pointing to another model, and _inherits tells Odoo to transparently expose all fields of the linked model as if they were your own fields. Two tables exist in the database, but the ORM presents them as one.
from odoo import models, fields
class LibraryMember(models.Model):
_name = 'library.member'
_description = 'Library Member'
# Mandatory: the field that points to the delegated model
partner_id = fields.Many2one(
'res.partner',
string='Contact',
required=True,
ondelete='cascade',
auto_join=True,
)
# Delegation: all res.partner fields (name, email, phone, address…)
# are accessible directly on library.member as if they were native
_inherits = {'res.partner': 'partner_id'}
# Library-specific fields (stored in library_member table)
card_number = fields.Char(string='Library Card Number')
membership_tier = fields.Selection([
('standard', 'Standard'),
('premium', 'Premium'),
], string='Membership Tier', default='standard')
# Usage — name and email come from res.partner transparently:
# member = env['library.member'].create({
# 'name': 'Jane Smith', # → writes to res.partner
# 'email': 'jane@example.com', # → writes to res.partner
# 'card_number': 'LIB-00042', # → writes to library.member
# })
| When to use delegation | When NOT to use delegation |
|---|---|
You want to extend res.partner (or similar) with a distinct identity | When classical inheritance is simpler and sufficient |
| You need the delegated record to exist independently (e.g. the partner survives member deletion) | When the two tables becoming out-of-sync would cause bugs |
| You want to reuse all the partner fields in forms and reports without copying them | When you don't need the parent model's features |
Inheriting from Multiple Models
A model can inherit from multiple models simultaneously by providing a list to _inherit. This is primarily used with mixins — abstract models that provide reusable behaviour without creating a table.
from odoo import models, fields
class LibraryBook(models.Model):
_name = 'library.book'
_description = 'Library Book'
# Inherit from multiple models — all are mixins (abstract)
_inherit = [
'mail.thread', # adds chatter (message log) to the form
'mail.activity.mixin', # adds activity scheduling (call, email, todo)
]
# mail.thread adds: message_ids, follower_ids, message tracking
# mail.activity.mixin adds: activity_ids, activity scheduling UI
name = fields.Char(string='Title', required=True, tracking=True)
state = fields.Selection([
('available', 'Available'),
('borrowed', 'Borrowed'),
('damaged', 'Damaged'),
], string='State', default='available', tracking=True) # tracking=True logs changes to chatter
Extending Methods (super())
When you override a method in classical inheritance, you almost always want to call the parent implementation first. Use Python's super() for this:
from odoo import models, fields, api
import logging
_logger = logging.getLogger(__name__)
class LibraryBook(models.Model):
_inherit = 'library.book'
# Override create — add custom logic before/after the standard create
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
# Pre-processing: generate a reference number if not provided
if not vals.get('ref'):
vals['ref'] = self.env['ir.sequence'].next_by_code('library.book')
# Call the parent implementation (does the actual INSERT)
records = super().create(vals_list)
# Post-processing: send a welcome message to followers
for record in records:
record.message_post(
body=f"Book '{record.name}' has been added to the library.",
message_type='notification',
)
_logger.info("Created library book: %s (id=%d)", record.name, record.id)
return records
def write(self, vals):
# Log state transitions
if 'state' in vals:
for record in self:
_logger.info(
"Book %s state: %s → %s",
record.name, record.state, vals['state']
)
return super().write(vals)
def unlink(self):
# Prevent deletion of books that are currently borrowed
for record in self:
if record.state == 'borrowed':
from odoo.exceptions import UserError
raise UserError(
f"Cannot delete '{record.name}' — it is currently borrowed."
)
return super().unlink()
In Odoo 17+, the preferred pattern for overriding create is @api.model_create_multi which receives a list of value dicts. The older @api.model + single dict pattern is deprecated. Using create_multi allows Odoo to batch-create records efficiently and is required for compatibility with bulk operations.
Summary
📋 Key Points
- Classical (
_inheritonly): extends the existing model in place — most common pattern for customisation - Prototype (
_inherit + _name): creates a new model starting from an existing one's structure — use sparingly - Delegation (
_inherits): embeds another model via FK, exposing its fields transparently — ideal forres.partnerextension - Always call
super()when overridingcreate,write, orunlinkunless you have a very specific reason not to - Use
@api.model_create_multi(not@api.model) for create overrides in Odoo 17+ mail.threadandmail.activity.mixinare the standard way to add the chatter and activity features to any model
FAQ
Yes. Since all classical inheritance extensions are merged into one class by Odoo's registry, any module can override any method on the merged class. The override order is determined by the module dependency order — a module that depends on yours will override your overrides. Always call super() to ensure the full override chain executes.
_inherit (singular, string) is used for classical and prototype inheritance — it names the model to extend or copy from. _inherits (plural, dict) is used for delegation inheritance — it maps a model name to the Many2one field name on your model that links to it. They are completely different mechanisms despite the similar names.
mail.thread adds: a message_ids One2many of messages (the chatter), follower tracking (message_follower_ids), automatic logging of field changes when tracking=True is set on a field, and the message_post() method for sending messages. It also adds the Chatter widget to any form view that includes <div class="oe_chatter">.
Both approaches are valid. Adding fields directly to res.partner (classical inheritance) is simpler and means all library-related partner data is in one place. Delegation inheritance (library.member with _inherits) makes more sense when a library member has a distinct lifecycle and many additional fields — it keeps the partner record clean and separates concerns. For most small modules, classical inheritance is sufficient.