Skip to content

Inheritance in Odoo ​

Examples target Odoo 18 and 19 unless marked. Platform covers Odoo 11 to 20.

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>

List View Extension ​

Odoo 18 renamed <tree> to <list>. Target <list> in position and xpath expressions on Odoo 18 and later, and <tree> on Odoo 17 and earlier.

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>
        
        <!-- Set row decorations on the list -->
        <list position="attributes">
            <attribute name="decoration-success">is_vip == True</attribute>
            <attribute name="decoration-warning">credit_limit > 0</attribute>
        </list>
        
    </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 the inline list of a one2many field (Odoo 17 and earlier: /tree) -->
        <xpath expr="//field[@name='order_line']/list" 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>
        
        <!-- Odoo 17 and earlier: use <tree position="attributes"> -->
        <list 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>
        </list>
        
    </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.

Built by Cybrosys Technologies.