Quick Answer
Stop data duplication nightmares (student model duplicating res.partner fields = broken data integrity). Extension Inheritance: Add fields to existing model (_inherit = 'res.partner' only, no _name). Fields added to same table. Use for: loyalty points on customers. Classical Inheritance: New model IS-A parent (_name = 'supplier' + _inherit = 'res.partner'). Separate tables linked by foreign key. Use for: Supplier IS a Partner but needs own table. Delegation Inheritance: Composition via Many2one (_inherits = {'{'res.partner': 'partner_id'{'}'}). Access parent fields without duplication (student.name → partner_id.name). Use for: Student uses Partner data without copying. Wrong choice = $12k-$40k rework.
The Inheritance Problem
Your D2C brand has a Student/Buyer management system. You know Odoo's res.partner model already stores name, email, phone, address. You need additional fields for students: enrollment date, course, grade.
Option A (Wrong): Duplicate Fields
class Student(models.Model):
_name = 'student'
name = fields.Char() # DUPLICATE of res.partner.name
email = fields.Char() # DUPLICATE of res.partner.email
phone = fields.Char() # DUPLICATE of res.partner.phone
enrollment_date = fields.Date()
Result: Massive data duplication. A student's name changes in res.partner, but not in your Student model. Data integrity is broken.
Option B (Inheritance): Reuse Parent Data
class Student(models.Model):
_name = 'student'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one('res.partner', required=True, ondelete='cascade')
enrollment_date = fields.Date()
course = fields.Char()
grade = fields.Float()
Result: Student has access to all res.partner fields (name, email, phone) without duplicating them. Data is stored once. Update a student's name in one place, it syncs everywhere.
We've implemented 150+ Odoo systems for D2C brands. The ones who understand model inheritance properly? Their code is clean, scalable, and maintainable. The ones who "just create duplicate fields"? We've spent 40+ hours fixing data integrity nightmares.
The Three Types of Odoo Model Inheritance
| Type | Syntax | Tables | Use Case |
|---|---|---|---|
| Extension | _inherit only | Single (extends existing) | Add fields to existing model |
| Classical | _name + _inherit | Multi (new + parent) | New model IS-A parent |
| Delegation | _inherits + Many2one | Multi (linked via FK) | Composition, access parent fields |
Type 1: Extension Inheritance (Single-Table)
What it is: Add new fields or methods to an EXISTING model without creating a new model.
class ResPartner(models.Model):
_inherit = 'res.partner' # Only _inherit, no _name
loyalty_points = fields.Integer(string='Loyalty Points')
preferred_payment = fields.Selection([
('credit_card', 'Credit Card'),
('paypal', 'PayPal'),
('bank_transfer', 'Bank Transfer'),
])
What Happens
✓ Odoo finds the existing res.partner model
✓ Adds loyalty_points and preferred_payment fields to the SAME database table
✓ All partner records now have these fields
✓ No new database table is created
Real D2C Example: Loyalty Tracking
class ResPartner(models.Model):
_inherit = 'res.partner'
loyalty_tier = fields.Selection([
('bronze', 'Bronze'),
('silver', 'Silver'),
('gold', 'Gold'),
('platinum', 'Platinum'),
], default='bronze', string='Loyalty Tier')
lifetime_value = fields.Float(
string='Lifetime Purchase Value',
compute='_compute_lifetime_value',
store=True
)
total_orders = fields.Integer(
string='Total Orders',
compute='_compute_total_orders',
store=True
)
@api.depends('order_ids')
def _compute_lifetime_value(self):
"""Calculate total value of all orders from this customer."""
for record in self:
record.lifetime_value = sum(order.amount_total
for order in record.order_ids)
@api.depends('order_ids')
def _compute_total_orders(self):
"""Count total orders from this customer."""
for record in self:
record.total_orders = len(record.order_ids)
When to Use Extension Inheritance
✓ Adding fields to core models (res.partner, product.product, sale.order)
✓ Adding computed fields or methods
✓ Extending functionality of existing models
✓ You want the fields in the same table as the original model
Type 2: Classical Inheritance (Multi-Table)
What it is: Create a NEW model that inherits all fields and methods from an existing model, but stores data in a separate table.
class Supplier(models.Model):
_name = 'supplier' # NEW model
_inherit = 'res.partner' # Inherits from res.partner
supplier_code = fields.Char()
lead_time_days = fields.Integer()
What Happens
✓ Supplier is a completely new model with its own database table
✓ Supplier inherits all fields from res.partner (name, email, etc.)
✓ Supplier adds its own fields (supplier_code, lead_time_days)
✓ When you create a supplier, it creates records in BOTH tables
Real D2C Example: Supplier Management
class Supplier(models.Model):
_name = 'supplier'
_inherit = 'res.partner' # Supplier IS a partner
supplier_code = fields.Char(string='Supplier Code', required=True)
lead_time_days = fields.Integer(string='Lead Time (days)')
minimum_order_qty = fields.Float(string='Min Order Qty')
average_rating = fields.Float(
string='Average Rating',
compute='_compute_average_rating'
)
purchase_order_ids = fields.One2many(
'purchase.order', 'supplier_id',
string='Purchase Orders'
)
@api.depends('purchase_order_ids')
def _compute_average_rating(self):
"""Calculate supplier rating from purchase order history."""
for record in self:
if record.purchase_order_ids:
ratings = [po.supplier_rating for po in record.purchase_order_ids
if po.supplier_rating]
record.average_rating = sum(ratings) / len(ratings) if ratings else 0
else:
record.average_rating = 0
Extension vs Classical Comparison
| Aspect | Extension Inheritance | Classical Inheritance |
|---|---|---|
| Syntax | _inherit only | _name + _inherit |
| Tables | 1 table (existing) | 2 tables (new + parent) |
| New Model? | No (extends existing) | Yes (new entity type) |
| Use Case | Add loyalty points to customers | Supplier IS a Partner |
Type 3: Delegation Inheritance (Composition)
What it is: Create a NEW model that accesses fields from a PARENT model through a Many2one relationship, without duplicating those fields.
class Student(models.Model):
_name = 'student'
_inherits = {'res.partner': 'partner_id'}
# REQUIRED: Many2one field linking to parent
partner_id = fields.Many2one(
'res.partner',
required=True,
ondelete='cascade',
auto_join=True,
index=True
)
# Custom fields for student
enrollment_date = fields.Date()
course_id = fields.Many2one('course')
grade = fields.Float()
How Access Works
student = self.env['student'].create({
'partner_id': 1000, # Link to existing partner
'enrollment_date': '2025-01-15',
'course_id': 10,
})
# Access partner fields through delegation
print(student.name) # Accesses partner_id.name (automatic delegation)
print(student.email) # Accesses partner_id.email
print(student.enrollment_date) # Direct field on student
Real D2C Example: Multi-Channel Vendor Management
class VendorAccount(models.Model):
_name = 'vendor.account'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one(
'res.partner',
required=True,
ondelete='cascade',
auto_join=True,
index=True
)
# Channel-specific fields
shopify_id = fields.Char(string='Shopify Vendor ID')
shopify_commission = fields.Float(string='Shopify Commission %')
amazon_seller_id = fields.Char(string='Amazon Seller ID')
amazon_commission = fields.Float(string='Amazon Commission %')
stripe_account_id = fields.Char(string='Stripe Account ID')
payment_method = fields.Selection([
('bank_transfer', 'Bank Transfer'),
('paypal', 'PayPal'),
('stripe', 'Stripe'),
])
total_sales = fields.Float(
string='Total Sales',
compute='_compute_total_sales',
store=True
)
# Access vendor data
vendor = self.env['vendor.account'].search([('shopify_id', '=', '12345')])
print(vendor.name) # From res.partner (delegated)
print(vendor.email) # From res.partner (delegated)
print(vendor.shopify_commission) # Direct field
When to Use Delegation Inheritance
✓ Need a new model that reuses parent fields
✓ Want to access parent fields transparently (student.name instead of student.partner_id.name)
✓ Parent fields shouldn't be duplicated
✓ Real examples: res.users delegates from res.partner, product.variant delegates from product.template
Real-World: All Three Types Together
Scenario: Fashion D2C brand managing Customers, Suppliers, and Affiliates
# Type 1: Extension Inheritance
# Add loyalty fields to existing res.partner
class ResPartner(models.Model):
_inherit = 'res.partner'
loyalty_points = fields.Integer(string='Loyalty Points')
vip_customer = fields.Boolean(string='VIP Customer')
# Type 2: Classical Inheritance
# Create new Supplier model, inherits from res.partner
class Supplier(models.Model):
_name = 'supplier'
_inherit = 'res.partner'
supplier_code = fields.Char()
lead_time = fields.Integer()
# Has access to res.partner fields (name, email, etc.)
# Stored in separate supplier table
# Type 3: Delegation Inheritance
# Create new Affiliate model, delegates to res.partner
class Affiliate(models.Model):
_name = 'affiliate'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one('res.partner', required=True, ondelete='cascade')
affiliate_code = fields.Char()
commission_rate = fields.Float()
total_referral_sales = fields.Float(compute='_compute_referral_sales')
Inheritance Decision Tree
| Question | Use This Type |
|---|---|
| Adding fields to existing model? | Extension |
| Creating new model that IS-A parent type? | Classical |
| Need parent fields accessible directly (no .field_id)? | Delegation |
| Want separate table but access parent fields? | Delegation |
Action Items: Build Proper Inheritance
Before Building
❏ Decide: Am I adding fields to an existing model, or creating a new model?
❏ If adding fields only → Use Extension Inheritance
❏ If creating new model → Decide between Classical or Delegation
❏ If new model needs parent fields accessible directly → Use Delegation
For Each Type
❏ Extension: Add _inherit = 'model.name' to existing model
❏ Classical: Add both _name = 'new.model' and _inherit = 'parent.model'
❏ Delegation: Add _inherits = {'{'parent.model': 'field_name'{'}'} + Many2one field
Test Your Inheritance
❏ Create a test record in the child model
❏ Verify parent fields are accessible
❏ Check database structure (1 table for Extension, 2 tables for Classical/Delegation)
❏ Verify data is not duplicated
Frequently Asked Questions
What's the difference between _inherit and _inherits?
_inherit: Extension (add fields to existing model, single table) or Classical (new model with _name, multi-table). _inherits: Delegation inheritance only. Uses dict syntax {'{'parent.model': 'field_name'{'}'}. Requires Many2one field. Accesses parent fields transparently without duplication (student.name → partner_id.name).
When should I use Classical vs Delegation inheritance?
Classical: Use when new model IS-A parent type and needs separate table for organizational reasons (Supplier IS a Partner). Both tables store data. Delegation: Use when new model needs to access parent fields without duplication (Student uses Partner data). Parent data accessed via Many2one link. Delegation is composition, Classical is true inheritance.
How many database tables are created for each type?
Extension: 1 table (adds fields to existing table). Classical: 2 tables (new model table + parent table, linked by hidden foreign key). Delegation: 2 tables (new model table + parent table, linked by Many2one field you define). Extension modifies existing, Classical and Delegation both create new tables.
What happens if I duplicate fields instead of using inheritance?
Data duplication = broken data integrity. Student model duplicates res.partner fields (name, email). Student's name changes in res.partner but not Student model. Data out of sync. Manual sync scripts required. $12k-$40k rework costs to fix after 100k records. Always use inheritance to access parent fields without duplication.
Free Model Inheritance Architecture Review
Stop building inheritance wrong. We'll assess your current inheritance structure, identify when Extension vs Classical vs Delegation should be used, spot data duplication issues, review for performance and scalability, and provide architecture recommendations. Most D2C brands discover their inheritance is wrong after they've built half the system. Fixing it costs $12,000-$40,000 in rework. Getting it right the first time matters.
