Inheritance in Odoo
Model Inheritance
Classical Inheritance (_inherit with _name)
python
# Base model
class BaseProduct(models.Model):
_name = 'base.product'
_description = 'Base Product'
name = fields.Char('Name', required=True)
price = fields.Float('Price', default=0.0)
active = fields.Boolean('Active', default=True)
# Child model inheriting from base
class ExtendedProduct(models.Model):
_name = 'extended.product'
_inherit = 'base.product' # Inherits all fields and methods
_description = 'Extended Product'
category = fields.Char('Category')
description = fields.Text('Description')
def custom_method(self):
return f"Extended product: {self.name}"
Extension Inheritance (_inherit only)
python
# Extending existing res.partner model
class PartnerExtension(models.Model):
_inherit = 'res.partner'
# Add new fields
customer_code = fields.Char('Customer Code', size=20)
credit_limit = fields.Float('Credit Limit', default=0.0)
last_purchase_date = fields.Date('Last Purchase Date')
is_vip = fields.Boolean('VIP Customer', default=False)
# Add new methods
def check_credit_limit(self):
"""Check if customer exceeds credit limit"""
return self.credit_limit > 0 and self.credit_limit < self.total_due
# Override existing methods
@api.model
def create(self, vals):
# Custom logic before creation
if 'customer_code' not in vals:
vals['customer_code'] = self._generate_customer_code()
return super().create(vals)
def _generate_customer_code(self):
"""Generate unique customer code"""
last_code = self.search([], order='id desc', limit=1).customer_code
if last_code:
number = int(last_code[3:]) + 1
else:
number = 1
return f"CUS{number:05d}"
Delegation Inheritance (_inherits)
python
# Parent model
class User(models.Model):
_name = 'res.users'
_inherits = {'res.partner': 'partner_id'} # Delegate to partner
partner_id = fields.Many2one('res.partner', required=True, ondelete='restrict')
login = fields.Char('Login', required=True)
password = fields.Char('Password')
# Can access partner fields directly
# self.name, self.email, etc. are available
# Child model using delegation
class Employee(models.Model):
_name = 'hr.employee'
_inherits = {'res.partner': 'address_id'}
address_id = fields.Many2one('res.partner', 'Address', required=True, ondelete='restrict')
employee_number = fields.Char('Employee Number', required=True)
department_id = fields.Many2one('hr.department', 'Department')
job_id = fields.Many2one('hr.job', 'Job Position')
# Inherited fields from res.partner: name, email, phone, etc.
Multiple Inheritance
python
class ProductMixin(models.AbstractModel):
_name = 'product.mixin'
_description = 'Product Mixin'
barcode = fields.Char('Barcode')
weight = fields.Float('Weight')
volume = fields.Float('Volume')
def calculate_shipping_cost(self):
return self.weight * 2.5 + self.volume * 1.2
class TrackingMixin(models.AbstractModel):
_name = 'tracking.mixin'
_description = 'Tracking Mixin'
created_date = fields.Datetime('Created Date', default=fields.Datetime.now)
modified_date = fields.Datetime('Modified Date')
created_by = fields.Many2one('res.users', 'Created By', default=lambda self: self.env.user)
@api.model
def create(self, vals):
vals['created_date'] = fields.Datetime.now()
return super().create(vals)
def write(self, vals):
vals['modified_date'] = fields.Datetime.now()
return super().write(vals)
# Model inheriting from multiple mixins
class AdvancedProduct(models.Model):
_name = 'advanced.product'
_inherit = ['product.template', 'product.mixin', 'tracking.mixin']
_description = 'Advanced Product'
advanced_features = fields.Text('Advanced Features')
def get_product_info(self):
info = {
'name': self.name,
'barcode': self.barcode,
'weight': self.weight,
'created_by': self.created_by.name,
'shipping_cost': self.calculate_shipping_cost()
}
return info
Abstract Models and Mixins
Abstract Base Model
python
class AbstractDocument(models.AbstractModel):
_name = 'abstract.document'
_description = 'Abstract Document Base'
name = fields.Char('Name', required=True)
reference = fields.Char('Reference')
state = fields.Selection([
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('done', 'Done'),
('cancelled', 'Cancelled')
], default='draft', string='State')
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company)
user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self.env.user)
def confirm_document(self):
self.state = 'confirmed'
def cancel_document(self):
self.state = 'cancelled'
# Concrete models inheriting from abstract
class SalesOrder(models.Model):
_name = 'sale.order'
_inherit = 'abstract.document'
_description = 'Sales Order'
partner_id = fields.Many2one('res.partner', 'Customer', required=True)
order_line = fields.One2many('sale.order.line', 'order_id', 'Order Lines')
amount_total = fields.Float('Total Amount', compute='_compute_amount_total')
@api.depends('order_line.price_subtotal')
def _compute_amount_total(self):
for order in self:
order.amount_total = sum(line.price_subtotal for line in order.order_line)
class PurchaseOrder(models.Model):
_name = 'purchase.order'
_inherit = 'abstract.document'
_description = 'Purchase Order'
partner_id = fields.Many2one('res.partner', 'Vendor', required=True)
order_line = fields.One2many('purchase.order.line', 'order_id', 'Order Lines')
amount_total = fields.Float('Total Amount', compute='_compute_amount_total')
@api.depends('order_line.price_subtotal')
def _compute_amount_total(self):
for order in self:
order.amount_total = sum(line.price_subtotal for line in order.order_line)
Utility Mixins
python
class MailMixin(models.AbstractModel):
_name = 'mail.mixin'
_description = 'Mail Mixin'
def send_notification(self, message, recipients):
"""Send email notification"""
self.env['mail.mail'].create({
'subject': f'Notification for {self.name}',
'body_html': message,
'email_to': ','.join(recipients),
}).send()
class SequenceMixin(models.AbstractModel):
_name = 'sequence.mixin'
_description = 'Sequence Mixin'
sequence = fields.Integer('Sequence', default=10)
@api.model
def create(self, vals):
if 'sequence' not in vals:
vals['sequence'] = self._get_next_sequence()
return super().create(vals)
def _get_next_sequence(self):
max_seq = self.search([], order='sequence desc', limit=1).sequence
return (max_seq or 0) + 10
class ArchiveMixin(models.AbstractModel):
_name = 'archive.mixin'
_description = 'Archive Mixin'
active = fields.Boolean('Active', default=True)
archived_date = fields.Datetime('Archived Date')
archived_by = fields.Many2one('res.users', 'Archived By')
def archive_record(self):
self.write({
'active': False,
'archived_date': fields.Datetime.now(),
'archived_by': self.env.user.id
})
def unarchive_record(self):
self.write({
'active': True,
'archived_date': False,
'archived_by': False
})
View Inheritance
Form View Extension
xml
<!-- Extending existing form 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">
<!-- Add field after existing field -->
<field name="phone" position="after">
<field name="customer_code"/>
<field name="credit_limit"/>
</field>
<!-- Add new page -->
<xpath expr="//notebook" position="inside">
<page string="Additional Info">
<group>
<field name="last_purchase_date"/>
<field name="is_vip"/>
</group>
</page>
</xpath>
<!-- Modify existing field attributes -->
<field name="email" position="attributes">
<attribute name="required">1</attribute>
<attribute name="placeholder">Enter email address</attribute>
</field>
<!-- Add field to existing group -->
<xpath expr="//group[@name='contact_details']" position="inside">
<field name="customer_rating"/>
</xpath>
</field>
</record>
Tree View Extension
xml
<record id="view_partner_tree_inherit" model="ir.ui.view">
<field name="name">res.partner.tree.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<!-- Add columns -->
<field name="email" position="after">
<field name="customer_code"/>
<field name="credit_limit"/>
</field>
<!-- Modify existing field -->
<field name="name" position="attributes">
<attribute name="string">Customer Name</attribute>
</field>
<!-- Add filter in tree -->
<tree position="attributes">
<attribute name="decoration-success">is_vip == True</attribute>
<attribute name="decoration-warning">credit_limit > 0</attribute>
</tree>
</field>
</record>
Search View Extension
xml
<record id="view_partner_search_inherit" model="ir.ui.view">
<field name="name">res.partner.search.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<!-- Add search fields -->
<field name="name" position="after">
<field name="customer_code"/>
</field>
<!-- Add filters -->
<filter name="supplier" position="after">
<separator/>
<filter string="VIP Customers" name="vip_customers" domain="[('is_vip', '=', True)]"/>
<filter string="With Credit Limit" name="with_credit" domain="[('credit_limit', '>', 0)]"/>
</filter>
<!-- Add group by -->
<xpath expr="//group" position="inside">
<filter string="VIP Status" name="group_vip" context="{'group_by': 'is_vip'}"/>
</xpath>
</field>
</record>
Complex View Modifications
xml
<record id="view_sale_order_form_complex_inherit" model="ir.ui.view">
<field name="name">sale.order.form.complex.inherit</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<!-- Replace entire section -->
<xpath expr="//group[@name='sale_header']" position="replace">
<group name="sale_header_custom">
<group>
<field name="partner_id" options="{'no_create': True}"/>
<field name="partner_invoice_id"/>
<field name="partner_shipping_id"/>
</group>
<group>
<field name="date_order"/>
<field name="validity_date"/>
<field name="payment_term_id"/>
</group>
</group>
</xpath>
<!-- Insert before specific element -->
<field name="state" position="before">
<field name="custom_status" widget="badge"/>
</field>
<!-- Modify tree view inside form -->
<xpath expr="//field[@name='order_line']/tree" position="inside">
<field name="custom_line_field"/>
</xpath>
<!-- Add button to header -->
<xpath expr="//header" position="inside">
<button name="custom_action" string="Custom Action" type="object" class="btn-primary"/>
</xpath>
</field>
</record>
Advanced Inheritance Patterns
Conditional Inheritance
python
class ConditionalProduct(models.Model):
_inherit = 'product.template'
# Conditional fields based on module installation
if 'stock' in [module.name for module in self.env['ir.module.module'].search([('state', '=', 'installed')])]:
stock_quantity = fields.Float('Stock Quantity')
# Conditional methods
def get_availability(self):
if hasattr(self, 'stock_quantity'):
return 'In Stock' if self.stock_quantity > 0 else 'Out of Stock'
return 'Stock module not installed'
Dynamic Inheritance
python
class DynamicModel(models.Model):
_name = 'dynamic.model'
@api.model
def _setup_complete(self):
super()._setup_complete()
# Dynamic field creation based on configuration
config = self.env['ir.config_parameter'].sudo()
if config.get_param('enable_custom_fields'):
self._add_custom_fields()
def _add_custom_fields(self):
"""Dynamically add fields based on configuration"""
custom_fields = self.env['custom.field.config'].search([])
for field_config in custom_fields:
field_type = getattr(fields, field_config.field_type.capitalize())
setattr(self.__class__, field_config.name, field_type(field_config.label))
Mixin Composition
python
class CompositeMixin(models.AbstractModel):
_name = 'composite.mixin'
_inherit = ['mail.mixin', 'sequence.mixin', 'archive.mixin']
_description = 'Composite Mixin'
def archive_and_notify(self, message="Record archived"):
"""Combine archive and notification functionality"""
self.archive_record()
if hasattr(self, 'send_notification'):
self.send_notification(message, [self.env.user.email])
class FullFeaturedModel(models.Model):
_name = 'full.featured.model'
_inherit = 'composite.mixin'
_description = 'Full Featured Model'
name = fields.Char('Name', required=True)
description = fields.Text('Description')
Method Override Patterns
Super() Pattern
python
class ProductExtended(models.Model):
_inherit = 'product.template'
@api.model
def create(self, vals):
# Pre-processing
if 'name' in vals:
vals['name'] = vals['name'].title()
# Call parent method
record = super().create(vals)
# Post-processing
record._update_related_records()
return record
def write(self, vals):
# Pre-processing
old_names = {rec.id: rec.name for rec in self}
# Call parent method
result = super().write(vals)
# Post-processing
if 'name' in vals:
for record in self:
if record.name != old_names.get(record.id):
record._handle_name_change(old_names[record.id])
return result
def unlink(self):
# Store data before deletion
names = self.mapped('name')
# Call parent method
result = super().unlink()
# Post-processing (cleanup)
self._cleanup_after_deletion(names)
return result
Method Decoration and Wrapping
python
class DecoratedModel(models.Model):
_inherit = 'sale.order'
def confirm_order(self):
"""Override with additional validation"""
# Validation before confirmation
self._validate_order_requirements()
# Call original method
result = super().confirm_order()
# Additional actions after confirmation
self._send_confirmation_email()
self._update_inventory_forecast()
return result
@api.constrains('amount_total')
def _check_amount_total(self):
"""Add additional constraints"""
super()._check_amount_total() # Call parent constraints if any
for order in self:
if order.amount_total > 100000:
if not order.partner_id.credit_limit or order.partner_id.credit_limit < order.amount_total:
raise ValidationError("Order amount exceeds customer credit limit")
Inheritance Best Practices
Proper Model Structure
python
class WellStructuredExtension(models.Model):
_inherit = 'res.partner'
_description = 'Partner Extension with Best Practices'
# Group related fields together
# Financial Information
credit_limit = fields.Float('Credit Limit', default=0.0, help="Maximum credit allowed")
payment_terms = fields.Char('Default Payment Terms')
# Customer Classification
customer_tier = fields.Selection([
('bronze', 'Bronze'),
('silver', 'Silver'),
('gold', 'Gold'),
('platinum', 'Platinum')
], string='Customer Tier', default='bronze')
# Computed fields
total_orders = fields.Integer('Total Orders', compute='_compute_total_orders', store=True)
# Constraints
@api.constrains('credit_limit')
def _check_credit_limit(self):
for partner in self:
if partner.credit_limit < 0:
raise ValidationError("Credit limit cannot be negative")
# Computed field methods
@api.depends('sale_order_ids')
def _compute_total_orders(self):
for partner in self:
partner.total_orders = len(partner.sale_order_ids)
# Business logic methods
def upgrade_customer_tier(self):
"""Business method to upgrade customer tier"""
tier_mapping = {
'bronze': 'silver',
'silver': 'gold',
'gold': 'platinum',
'platinum': 'platinum' # Already at highest tier
}
self.customer_tier = tier_mapping.get(self.customer_tier, 'bronze')
View Inheritance Organization
xml
<!-- Organized view inheritance -->
<record id="view_partner_form_financial_inherit" model="ir.ui.view">
<field name="name">res.partner.form.financial.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<!-- Financial Information Section -->
<xpath expr="//notebook" position="inside">
<page string="Financial Info" name="financial_info">
<group string="Credit Management">
<field name="credit_limit"/>
<field name="payment_terms"/>
<field name="customer_tier"/>
</group>
<group string="Statistics">
<field name="total_orders" readonly="1"/>
</group>
</page>
</xpath>
</field>
</record>
<!-- Separate inheritance for different concerns -->
<record id="view_partner_tree_tier_inherit" model="ir.ui.view">
<field name="name">res.partner.tree.tier.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<field name="email" position="after">
<field name="customer_tier" widget="badge"/>
</field>
<tree position="attributes">
<attribute name="decoration-warning">customer_tier == 'bronze'</attribute>
<attribute name="decoration-info">customer_tier == 'silver'</attribute>
<attribute name="decoration-success">customer_tier in ('gold', 'platinum')</attribute>
</tree>
</field>
</record>
Module Dependencies and Inheritance
python
# __manifest__.py
{
'name': 'Customer Extensions',
'depends': ['base', 'sale', 'account'], # Clear dependencies
'auto_install': False,
'installable': True,
}
# models/partner.py
class PartnerExtension(models.Model):
_inherit = 'res.partner'
# Safe inheritance with dependency checks
@api.model
def _setup_complete(self):
super()._setup_complete()
# Check if sale module is installed
if 'sale.order' in self.env:
self._setup_sale_extensions()
# Check if account module features
if 'account.move' in self.env:
self._setup_accounting_extensions()
def _setup_sale_extensions(self):
"""Setup sale-related extensions"""
pass
def _setup_accounting_extensions(self):
"""Setup accounting-related extensions"""
pass
Performance Considerations
Efficient Inheritance
python
class EfficientInheritance(models.Model):
_inherit = 'product.template'
# Use appropriate field types and options
computed_field = fields.Float('Computed Field', compute='_compute_field', store=True)
related_field = fields.Char('Related Field', related='category_id.name', store=True)
# Optimize computed fields
@api.depends('list_price', 'standard_price')
def _compute_field(self):
# Efficient computation
for product in self:
product.computed_field = product.list_price - product.standard_price
# Batch operations
def bulk_update_prices(self, price_increase_percent):
"""Efficient bulk update"""
price_factor = 1 + (price_increase_percent / 100)
self.write({'list_price': self.list_price * price_factor})
Memory-Efficient Patterns
python
class MemoryEfficientModel(models.Model):
_inherit = 'stock.picking'
def process_large_dataset(self):
"""Process large datasets efficiently"""
# Process in batches to avoid memory issues
batch_size = 100
total_records = self.search_count([])
for offset in range(0, total_records, batch_size):
batch = self.search([], limit=batch_size, offset=offset)
batch._process_batch()
# Clear cache periodically
self.env.clear()
def _process_batch(self):
"""Process a single batch"""
for picking in self:
picking._individual_processing()
Testing Inheritance
Unit Tests for Inherited Models
python
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError
class TestPartnerInheritance(TransactionCase):
def setUp(self):
super().setUp()
self.partner_model = self.env['res.partner']
self.test_partner = self.partner_model.create({
'name': 'Test Customer',
'email': 'test@example.com',
'customer_tier': 'bronze'
})
def test_credit_limit_constraint(self):
"""Test credit limit constraint"""
with self.assertRaises(ValidationError):
self.test_partner.credit_limit = -100
def test_tier_upgrade(self):
"""Test customer tier upgrade"""
initial_tier = self.test_partner.customer_tier
self.test_partner.upgrade_customer_tier()
self.assertNotEqual(self.test_partner.customer_tier, initial_tier)
def test_computed_fields(self):
"""Test computed field calculations"""
# Create sale orders for the partner
self.env['sale.order'].create({
'partner_id': self.test_partner.id,
'state': 'sale'
})
# Check computed field
self.assertEqual(self.test_partner.total_orders, 1)
Documentation and Maintenance
Inheritance Documentation
python
class DocumentedInheritance(models.Model):
"""
Extension of res.partner model to add customer management features.
This inheritance adds:
- Credit limit management
- Customer tier classification
- Enhanced customer statistics
- Automated tier upgrade functionality
Dependencies:
- base: Core partner functionality
- sale: For order statistics (optional)
- account: For financial information (optional)
"""
_inherit = 'res.partner'
_description = 'Partner Customer Management Extension'
# Fields documentation
credit_limit = fields.Float(
'Credit Limit',
default=0.0,
help="Maximum credit amount allowed for this customer. "
"Set to 0 for unlimited credit."
)
def upgrade_customer_tier(self):
"""
Upgrade customer to next tier level.
Tier progression: Bronze -> Silver -> Gold -> Platinum
Platinum customers remain at the same tier.
Returns:
str: New tier level after upgrade
"""
# Implementation here
pass
This comprehensive guide covers all aspects of inheritance in Odoo, from basic model extension to complex inheritance patterns, view modifications, and best practices for maintainable code.