Skip to content

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.