この記事は現在英語版のみです。翻訳は近日公開予定です。
By the end of this recipe, you will know how to extend any existing Odoo method — create, write, _post, action_confirm, custom validation logic — using the inheritance + super() pattern, with a clear understanding of when to override, when to extend, and when to use a different mechanism entirely. Skill required: solid Python OOP knowledge + Odoo basics. Time required: 30 minutes for a simple override, 90 minutes for a complex multi-inheritance case. ECOSIRE has shipped thousands of method overrides in production, and the recipe below is the playbook.
The mistake most new Odoo developers make: they replace core methods instead of extending them. The replace pattern silently breaks every other module that depends on the original method's behavior. The recipe below uses Python's super() correctly so your customization composes with everyone else's.
What you will need
- Odoo version: 17, 18, or 19. The
_inheritmechanism is identical. - Python OOP: comfort with class methods, MRO (Method Resolution Order), and
super(). - Custom module skeleton.
- Time: 30 minutes to 90 minutes depending on complexity.
Step-by-step
1. Identify the method to override
Find the original method in the Odoo source. For account.move._post:
grep -rn "def _post" /opt/odoo/addons/account/models/account_move.py
# Output points to the line. Read the docstring + the body to understand what it does.
Verification: you can articulate in 1 sentence what the original method does and where the extension point should be.
2. Add to module manifest
{
'name': 'Account Move Customization',
'version': '19.0.1.0.0',
'depends': ['account'], # Always depend on the module you're extending
...
}
Verification: module installs without dependency errors.
3. Override with super() (the canonical pattern)
from odoo import models, api
class AccountMove(models.Model):
_inherit = 'account.move'
def _post(self, soft=True):
# 1. Pre-super logic: anything that should happen BEFORE the original method
for move in self:
if move.move_type == 'out_invoice' and not move.partner_id.email:
raise UserError("Cannot post invoice for partner without email.")
# 2. Call the original method via super()
result = super()._post(soft=soft)
# 3. Post-super logic: anything that should happen AFTER
for move in self.filtered(lambda m: m.move_type == 'out_invoice' and m.state == 'posted'):
move._send_to_my_custom_archive_system()
return result
The super() call ensures every other module's override of _post (and the original) all execute. Your code wraps theirs.
Verification: post an invoice; both your pre-check fires and your post-archival fires.
4. Override create and write (most common cases)
For create, since Odoo 17 you must use @api.model_create_multi for batch-create support:
class ResPartner(models.Model):
_inherit = 'res.partner'
@api.model_create_multi
def create(self, vals_list):
# Pre-create: validate or default-set
for vals in vals_list:
if vals.get('email') and 'gmail.com' in vals['email']:
vals['customer_segment'] = 'consumer'
# Call super to actually create
records = super().create(vals_list)
# Post-create: side effects
for record in records:
record._send_welcome_notification()
return records
def write(self, vals):
# Detect specific changes
if 'customer_segment' in vals:
for record in self:
if record.customer_segment != vals['customer_segment']:
record._log_segment_change(record.customer_segment, vals['customer_segment'])
# Call super
return super().write(vals)
Verification: create a partner with gmail.com email; segment is auto-set to consumer.
5. Override using _inherit + _inherits (delegation)
When you want to add fields from another model without rewriting them, use _inherits (note the trailing s):
class CustomEmployee(models.Model):
_name = 'custom.employee'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one('res.partner', required=True, ondelete='cascade')
employee_number = fields.Char()
custom.employee automatically gets every res.partner field as if it had them, but they're stored on the linked partner record.
Verification: a custom.employee.create({'name': 'Joe', 'employee_number': 'E001'}) creates both records.
6. Override the _search method (advanced)
To customize how a model is searched (e.g., by computed fields), override _search:
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
# Translate a virtual filter into a real domain
new_args = []
for arg in args:
if isinstance(arg, (list, tuple)) and len(arg) == 3 and arg[0] == 'is_strategic':
if arg[2]:
new_args.append(('customer_segment', '=', 'strategic'))
else:
new_args.append(('customer_segment', '!=', 'strategic'))
else:
new_args.append(arg)
return super()._search(new_args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
Verification: a search domain [('is_strategic', '=', True)] returns only strategic customers.
7. Use @api.depends on inherited compute fields
If you override a computed field's compute method, preserve the dependency tree:
@api.depends('sale_order_ids.amount_untaxed', 'sale_order_ids.state', 'is_blacklisted')
def _compute_total_revenue(self):
super()._compute_total_revenue()
# Add additional logic
for partner in self:
if partner.is_blacklisted:
partner.total_revenue = 0
Verification: a blacklisted customer shows zero revenue regardless of their actual orders.
8. Test the inheritance chain
Use odoo-bin shell to inspect:
self.env['account.move'].mro()
# Returns the inheritance chain - your module should appear in the order it was loaded
Verify your override is in the chain. Verification: mro() shows your custom class between account.account_move.AccountMove and models.Model.
Common mistakes
- Forgetting
super(). Your override completely replaces the original, breaking every other module. - Calling
super()with the wrong arguments. Always match the signature exactly.super().create(vals_list)notsuper().create(*vals_list). - Adding side effects in compute methods. Compute methods should be pure functions of their dependencies. Use
@api.depends_contextor hooks instead. - Modifying
vals_listin place during create. Mutates the dict — fine if you intend it but a foot-gun if not. - Forgetting
@api.model_create_multidecorator. In Odoo 17+,createdefaults to batch mode.
Going further
Multiple inheritance: a model can _inherit from multiple parents. MRO determines the order; methods compose top-down. Use sparingly — multiple inheritance with conflicting parent methods is a debugging nightmare.
Hook methods: many Odoo methods provide explicit hooks (_action_done, _post_after_check, _compute_amount). Prefer extending these over the main method when possible. Hooks are the framework's way of saying "extend here". Reading the parent class shows you the available hooks.
Decorators: preserve @api.depends, @api.constrains, @api.onchange when extending. Stripping them silently breaks the framework. If your override adds new dependencies, add them via @api.depends('new_field') on a separate compute method.
Method composition strategy: prefer many small overrides each adding one concern over one giant override doing everything. Each override should be testable in isolation. Use docstrings to explain why this override is needed (audit, business rule, integration).
Conditional override: use if self.env.context.get('skip_my_override'): to provide an escape hatch. Useful for migration scripts that need to bypass business logic.
Replacing a method (when truly needed): drop the super() call. But document loudly why and ensure your replacement covers all the parent's edge cases.
Pre-step vs post-step decisions: pre-super logic runs BEFORE the original (validate, transform inputs). Post-super runs AFTER (side effects on results). Most overrides are post-super; pre-super is for input validation.
Avoiding infinite recursion: if your override writes to a field that triggers the same method, you can hit infinite recursion. Use with_context(skip_my_logic=True) and check the context flag.
Performance considerations: every override adds a function call to the chain. For methods called millions of times (record cache lookups, session loads), profile before and after to confirm the override didn't add unacceptable latency.
Testing inheritance chains: write tests that exercise the full chain — install your module + a peer module that also overrides the same method, then verify both extensions fire in the correct order.
Documentation for downstream: if your module is consumed by others, document which methods you override and what behavior you add. Saves them debug time when they encounter unexpected behavior.
For complex inheritance patterns including chain-of-responsibility and observer-like extensibility, ECOSIRE Odoo customization ships engineered solutions. Pair this with how to write an Odoo unit test.
Frequently Asked Questions
When should I override vs replace?
Always override (with super()). Replacement breaks the ecosystem of other modules touching the same method. The only exception is when you've intentionally forked Odoo and own the entire stack — and even then, document why.
What's the difference between _inherit, _inherits, and _name?
_inherit = 'parent'extends the parent in place — you operate on the same database table._inherits = {'parent': 'parent_id'}delegates fields from a parent record — you have your own table._name = 'foo'creates a brand-new model.
The third (_name only) is rare in custom modules; mostly used for transient wizards.
How do I see all overrides of a method?
grep -rn "def _post" /opt/odoo /opt/odoo-enterprise /opt/custom-addons shows every override. Read them in load order to understand the full behavior. Or in odoo-bin shell, inspect the MRO: Model.mro().
Can I undo an override at runtime?
Not cleanly. Override the method to no-op based on a config parameter if you need a feature flag. Pattern: if self.env['ir.config_parameter'].get_param('my_module.disabled') == 'True': return super()....
How do I extend a private method (starting with _)?
Same as public — _inherit + super(). The convention is private but the framework doesn't enforce it. If you override a private method, be aware it can change without notice in major versions.
What happens if I forget super()?
Your override completely replaces the original. Other modules' overrides also become irrelevant because they were calling super() to reach the original. Effectively breaks the inheritance chain for everything below your module.
Can I override Odoo's framework methods (e.g., BaseModel.read)?
Yes — _inherit = 'base' extends every model. Use very sparingly because it affects performance for all model reads.
How do I add a method without overriding?
Just define it in your inheriting class. New methods aren't an override; they're additions: def my_new_method(self): ....
What's pre_init_hook?
A function called once at module install (before any data files load). Used for one-time setup like creating database extensions or seeding non-XML data. Different from migrations (which run on version bumps).
How do I detect which class actually owns the method?
Model._fields['my_field'].comodel.__class__ shows the inheritance chain. Useful for debugging when you're unsure which override fires.
Can I use mixins?
Yes — Odoo's mail.thread and mail.activity.mixin are mixins. Define your own with _name = 'my.mixin' (and no _inherit) and let other models _inherit = ['model.parent', 'my.mixin'].
How do I delegate work to other models?
Use self.env['other.model'] to call methods on related models. Don't hardcode references — use self.env.ref('module.xml_id') for stable references.
For complex Odoo customizations including dynamic feature flags and A/B testing of business logic, ECOSIRE Odoo customization builds the entire stack. Or read how to add a custom field without Studio.
執筆者
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
関連記事
Odoo フォーム ビューにカスタム ボタンを追加する方法 (2026)
Odoo 19 フォーム ビューにカスタム アクション ボタンを追加します: Python アクション メソッド、ビューの継承、条件付き可視性、確認ダイアログ。製造テスト済み。
Studio を使用せずに Odoo にカスタムフィールドを追加する方法 (2026)
Odoo 19 のカスタム モジュールを介してカスタム フィールドを追加します: モデル継承、ビュー拡張、計算フィールド、ストア/非ストアの決定。コードファースト、バージョン管理。
外部レイアウトを使用して Odoo にカスタム レポートを追加する方法
web.external_layout: QWeb テンプレート、paperformat、アクション バインディングを使用して、Odoo 19 でブランド化された PDF レポートを構築します。印刷ロゴ + フッターのオーバーライド付き。