Complete Developer Tutorial
First, init hooks give you fine-grained control over the Odoo module lifecycle. Moreover, init hooks (also called initialization hooks or module entry points) let you run custom Python code at precise moments during installation, upgrade, or removal. Additionally, this tutorial shows you how to leverage init hooks to seed initial data, verify dependencies, and patch Odoo behavior. Furthermore, you register init hooks in your module’s manifest file (__manifest__.py
) to activate them. Also, you can explore further details in the Odoo Developer Documentation.
https://tasintarek.odoo.com/blog/backend-4/understanding-init-hooks-in-odoo-3
Understanding Initialization Hooks in Odoo
First, developers should understand that initialization hooks (often called lifecycle hooks) fire at key stages of an Odoo module’s life. Next, init hooks act as entry points where Odoo pauses the normal flow to run custom code. Then, you can enforce checks, adjust the database schema, or prepare data before the ORM loads models. Moreover, after the ORM populates your models, Odoo fires another hook that lets you seed or migrate data. Also, when you uninstall the module, Odoo triggers a cleanup hook. Finally, Odoo triggers a post-load hook once all modules load, which lets you patch or extend core behavior before any model initializes.
First, you maintain clear separation between model definitions and lifecycle logic. Next, you avoid mixing post-install scripts with XML data files. Then, you achieve robust module lifecycle management. Therefore, initialization hooks provide the best way to insert custom logic at the right time.
Hook Execution Order and Lifecycle
First, Odoo reads your module’s manifest and gathers hook names. Next, Odoo fires the pre_init_hook before it processes any Python models or XML/CSV files. Then, Odoo loads model definitions and applies <data>
files. After that, the post_init_hook executes right after data import. Later, when you uninstall your module, Odoo calls the uninstall_hook after dropping tables and records. Finally, after Odoo loads all modules and dependencies, it fires the post_load hook on server startup. Consequently, each hook plays a distinct role in your module setup and teardown.
First, the pre_init_hook helps you validate or prepare. Next, the post_init_hook seeds and migrates data. Then, the uninstall_hook cleans up leftover items. Lastly, the post_load hook handles cross-module patches or global changes. Moreover, you can sequence custom logic by combining hooks strategically.
Registering Init Hooks in Your Manifest
First, you declare init hooks in your module’s __manifest__.py
file. Next, you map hook names to Python functions in a hooks.py
file. Then, Odoo automatically picks up these names and executes the linked functions. Moreover, you can omit unused hooks by leaving them out of the manifest.
# __manifest__.py
{
'name': 'Custom Lifecycle Module',
'version': '2.0',
'author': 'Your Name',
'category': 'Tools',
'summary': 'Module demonstrating init hooks',
'depends': ['base'],
'pre_init_hook': 'validate_dependencies',
'post_init_hook': 'seed_initial_data',
'uninstall_hook': 'cleanup_logs',
'post_load': 'apply_global_patches',
'data': ['views/custom_view.xml'],
}
First, Odoo reads pre_init_hook: 'validate_dependencies'
. Next, Odoo loads your hooks.py
file. Then, it calls the validate_dependencies
function before model loading. Therefore, you enforce dependencies early.
Lifecycle Hooks Deep Dive
First, you dig into each init hook signature and use case. Next, you see how to implement, test, and debug each hook. Then, you compare hook roles to choose the right one for your logic.
Pre-install Hook (pre_init_hook)
First, the pre_init_hook fires right before Odoo creates tables or loads data. Next, you can use it to validate database state or enforce module prerequisites. Then, you can run raw SQL or low-level operations safely. Moreover, you can create or alter tables before the ORM interacts with them.
Signature and Parameters
- Signature:
def pre_init_hook(cr):
- Parameter:
cr
: low-level database cursor.
Common Use Cases
- Dependency Checks: Halt installation when prerequisites are missing.
- Schema Preparation: Manually create or alter tables.
- Feature Flags: Check config flags outside ORM.
Sample Code
# hooks.py
from odoo import api, SUPERUSER_ID
def validate_dependencies(cr):
"""Check required modules before install."""
env = api.Environment(cr, SUPERUSER_ID, {})
required = ['sale_management']
for module in required:
record = env['ir.module.module'].search([
('name', '=', module),
('state', '=', 'installed'),
])
if not record:
raise ValueError(f"{module} must be installed first.")
First, this code builds an ORM environment from cr
. Next, it searches for installed modules. Then, it raises an error if a module is not installed.
Post-install Hook (post_init_hook)
First, the post_init_hook executes after Odoo loads your module’s models and XML/CSV data. Next, you can seed default records or migrate existing data. Then, you enjoy direct access to the fully loaded environment.
Signature and Parameters
- Signature:
def post_init_hook(cr, registry):
- Parameters:
cr
: database cursor.registry
: new registry of models.
Common Use Cases
- Data Seeding: Create default records.
- Data Migration: Migrate values from old models.
- Auto-Configuration: Set default settings or preferences.
Sample Code
# hooks.py
from odoo import api, SUPERUSER_ID
def seed_initial_data(cr, registry):
"""Seed default configuration and demo data."""
env = api.Environment(cr, SUPERUSER_ID, {})
# Demo partner
env['res.partner'].create({
'name': 'Demo Customer',
'email': 'demo@example.com',
})
# Default settings
env['res.config.settings'].set_values({
'module_auto_confirm_order': True,
})
First, the hook creates a demo partner. Next, it applies default settings. Then, it logs progress automatically.
Cleanup Hook (uninstall_hook)
First, the uninstall_hook runs after Odoo removes module tables and data. Next, you use it to clean records outside your module namespace. Then, you notify third-party services about removal.
Signature and Parameters
- Signature:
def uninstall_hook(cr, registry):
- Parameters:
cr
: database cursor.registry
: registry of models at uninstall.
Common Use Cases
- Orphan Data Cleanup: Remove external logs.
- Archive Records: Move records to an archive table.
- Integration Notification: Inform external systems.
Sample Code
# hooks.py
from odoo import api, SUPERUSER_ID
def cleanup_logs(cr, registry):
"""Remove or archive leftover logs."""
env = api.Environment(cr, SUPERUSER_ID, {})
logs = env['my.module.log'].search([])
logs.unlink()
# Send removal notice
import requests
requests.post('https://api.example.com/hook_remove', json={
'module': 'custom_lifecycle_module',
})
First, this function deletes all log entries. Next, it sends an HTTP POST notification. Then, it completes without error.
Post-load Hook (post_load)
First, the post_load hook fires after Odoo imports all Python modules, before any model initialization. Next, you can override core methods or add dynamic behavior. Then, you ensure patches apply once per server start.
Signature and Parameters
- Signature:
def post_load():
- Parameters: None.
Common Use Cases
- Monkey Patching: Override methods in core or other addons.
- Global Configuration: Set global defaults.
- Dynamic Behavior: Attach signals or listeners.
Sample Code
# hooks.py
def apply_global_patches():
"""Monkey-patch core rounding behavior."""
from odoo.addons.base.models.res_currency import ResCurrency
original_round = ResCurrency.round
def patched_round(self, amount, digits=None):
result = original_round(self, amount, digits)
return result + 0.01
ResCurrency.round = patched_round
First, you import the target class. Next, you store the original method. Then, you assign the new method.
Best Practices for Init Hooks
First, keep init hooks lightweight to avoid slowing down installation. Next, avoid heavy SQL loops inside pre_init_hook. Then, move large data migrations to separate scripts or <data>
XML files. Moreover, use the ORM via api.Environment
instead of raw SQL whenever possible to improve readability and safety. Also, wrap risky code in try/except
blocks and log clear error messages to guide administrators. Therefore, you maintain fast, stable, and reliable installs and updates.
Furthermore, prefer descriptive hook names over generic ones. For instance, use validate_custom_dependency
rather than pre_init_hook
. Next, document every hook in your module’s changelog or README. Then, inform maintainers about new hooks in upgrades. Moreover, test your init hooks with automated scripts to catch regressions early.
Finally, consider the scalability of your hooks. For example, if you seed records for thousands of companies, you may want to batch operations or use asynchronous jobs. Also, respect user context and multi-company environments to avoid data leaks.
Troubleshooting and Debugging Init Hooks
First, when an init hook fails, Odoo rolls back the current transaction and aborts the operation. Next, you should check the server log (odoo.log
or terminal output) for error traces. Then, you can add logging statements in your hook functions to print debug information. Moreover, you can use Python’s built-in logging
module:
import logging
_logger = logging.getLogger(__name__)
def validate_dependencies(cr):
_logger.info("Starting validate_dependencies pre_init_hook")
# ...
_logger.info("Completed dependency check")
First, logging helps you trace hook execution order. Next, you can open an interactive shell via odoo shell
to test hook code in isolation. Then, you can import your hook functions in the shell and run them manually:
$ odoo shell -d my_database
>>> from my_module.hooks import validate_dependencies
>>> validate_dependencies(env.cr)
Moreover, you can write unit tests to automate hook validation. Use Odoo’s TransactionCase
:
from odoo.tests.common import TransactionCase
class TestInitHooks(TransactionCase):
def test_pre_init(self):
with self.assertRaises(ValueError):
self.module.validate_dependencies(self.cr)
def test_post_init(self):
self.module.seed_initial_data(self.cr, self.registry)
self.assertTrue(self.env['res.partner'].search([('name', '=', 'Demo Customer')]))
First, testing catches hook errors early. Next, you maintain quality across versions. Then, you reduce manual debugging time.
Testing Init Hooks with Automated Scripts
First, you automate init hook tests using Odoo’s testing framework or PyTest Plugin. Next, you create a tests
directory in your module. Then, you add a test file named test_hooks.py
.
tests/test_hooks.py
from odoo.tests.common import TransactionCase
from my_module import hooks
class TestLifecycleHooks(TransactionCase):
def test_pre_init_hook_runs(self):
with self.assertRaises(ValueError):
hooks.validate_dependencies(self.cr)
def test_post_init_hook_creates_data(self):
# Simulate post_init_hook
hooks.seed_initial_data(self.cr, self.registry)
partner = self.env['res.partner'].search([
('email', '=', 'demo@example.com')
])
self.assertEqual(len(partner), 1)
def test_uninstall_hook_cleans_logs(self):
# prepare a log entry
self.env['my.module.log'].create({'message': 'Test'})
hooks.cleanup_logs(self.cr, self.registry)
logs = self.env['my.module.log'].search([])
self.assertEqual(len(logs), 0)
First, this script tests pre_init_hook
, post_init_hook
, and uninstall_hook
. Next, you run tests with:
$ pytest --addons=my_module --maxfail=1 --disable-warnings -q
Then, you confirm that hooks behave as expected across Odoo versions. Moreover, you integrate tests into your CI pipeline for continuous validation.
Real-world Use Cases for Lifecycle Hooks
First, many real modules rely on init hooks to automate critical tasks. Next, you can divide use cases into several categories:
Data Seeding and Demo Content
First, post_init_hook seeds default products, customers, or configuration. Next, it enables fresh databases to include sample data for demos. Then, you avoid manual data entry.
def seed_demo_data(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
products = [{'name': 'Demo Widget', 'list_price': 10.0}]
for prod in products:
env['product.product'].create(prod)
Dependency Enforcement
First, pre_init_hook ensures that required modules exist before install. Next, you raise a clear ValueError
. Then, you avoid broken feature sets.
Data Migration and Cleanup
First, post_init_hook migrates custom field values when you upgrade the module. Next, it preserves historical data transformations. Then, it runs only once during upgrades.
Core Behavior Patching
First, post_load hook patches methods in Odoo’s core to adjust behavior globally. Next, it modifies UI widgets or model methods without altering base code.
def patch_invoice_behavior():
from odoo.addons.account.models.account_invoice import AccountInvoice
original_action_invoice = AccountInvoice.action_invoice_open
def new_action_invoice(self):
# custom pre-process
return original_action_invoice(self)
AccountInvoice.action_invoice_open = new_action_invoice
First, this approach avoids direct core edits. Next, it keeps your customizations modular. Then, you can easily revert patches by updating your hook.
Common Pitfalls and How to Avoid Them
First, developers often confuse post_load with post_init_hook. Next, remember that post_load runs on server start, not on install. Then, avoid putting data seeding in post_load. Moreover, do not call ORM methods in post_load that depend on registry
before it initializes.
First, another pitfall involves missing manifest keys. Next, double-check that your function names in __manifest__.py
match actual function names. Then, ensure you import your hooks.py
in __init__.py
to make the functions discoverable.
First, some developers forget to handle multi-database environments. Next, your pre_init_hook should run safely across multiple DBs without side effects. Then, you can scope operations to the current database only.
Finally, be careful with monkey patches. For instance, patching critical core methods without proper testing can break unrelated modules. Therefore, always include tests for patched behavior. Moreover, document every patch with rationales and references in your code comments.
Combining Init Hooks with Scheduled Jobs
First, init hooks serve well for one-time tasks on install or upgrade. Next, for recurring tasks, use cron jobs configured via XML or code. Then, you can schedule data cleanup, notifications, or sync tasks.
Example: Schedule a Daily Cleanup
First, you define a cron record in data/cron_data.xml
:
<odoo>
<data noupdate="1">
<record id="ir_cron_cleanup_logs" model="ir.cron">
<field name="name">Daily Cleanup Logs</field>
<field name="model_id" ref="model_my_module_log"/>
<field name="state">code</field>
<field name="code">model.cleanup_logs()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
</record>
</data>
</odoo>
First, you seed this record in post_init_hook. Next, Odoo executes cleanup_logs
daily. Then, you ensure regular maintenance without manual effort.
Use Case: Data Archival
First, you archive old records monthly. Next, you combine a post_init_hook to set up the cron and an external script for heavy archivals. Then, you avoid long installs by shifting heavy tasks to scheduled jobs.
Comparing Init Hooks to Model-level Hooks
First, Odoo offers model-level hooks such as @api.model_cr
or @api.model_add
. Next, init hooks focus on module lifecycle, not individual model events. Then, you should choose:
- init hooks for global tasks at install/update/uninstall.
- model hooks for model-specific triggers during record operations.
First, you avoid conflating module setup with business logic. Next, you keep code modular and testable. Then, you maintain clear separation between installation logic and runtime behavior.
Hook Signature Changes in Recent Odoo Versions
First, Odoo 15 changed the signature of post_init_hook to accept additional parameters. Next, you must update your hook definitions accordingly:
- Odoo 14:
def post_init_hook(cr, registry):
- Odoo 15+:
def post_init_hook(cr, registry, context=None):
Then, if you upgrade a module from Odoo 14 to 15, refactor your hook:
def seed_initial_data(cr, registry, context=None):
env = api.Environment(cr, SUPERUSER_ID, context or {})
# your logic
First, always test hooks after major upgrades. Next, consult official docs for signature updates. Then, maintain backward compatibility with conditional code:
import inspect
if len(inspect.signature(seed_initial_data).parameters) == 3:
kwargs = {'context': {}}
else:
kwargs = {}
seed_initial_data(cr, registry, **kwargs)
Performance and Security Considerations
First, heavy operations in pre_init_hook can slow down installations significantly. Next, you should avoid full table scans in large databases. Then, you can optimize queries by limiting record sets or using indexed fields. Moreover, move resource-intensive migrations to offline scripts or cron jobs.
First, patching core methods via post_load can introduce security risks. Next, you should review patches for SQL injection or unauthorized access. Then, ensure that only trusted admins edit hook code. Moreover, use Odoo’s access rights and record rules to enforce security.
Additionally, avoid hardcoding URLs or secrets in hooks. Instead, fetch configuration from ir.config_parameter
. Then, rotate keys securely without redeploying your module. Finally, document any security implications in your module’s README.
Migrating Hooks Across Odoo Versions
First, Odoo’s hook signatures may change across major releases. Next, you should review release notes when upgrading from Odoo 12 to 14 or beyond. Then, update your hook definitions to match new parameters or methods. Moreover, test hooks in a staging environment before production rollout.
First, check if post_load
moved or changed in Odoo 15+. Next, refactor your code accordingly. Then, remove deprecated imports and replace them with updated module paths. Additionally, update __manifest__.py
fields to align with the new manifest schema. Finally, run your automated tests to catch issues quickly.
Therefore, planning migration ensures seamless upgrades and keeps init hooks functional across versions.
Conclusion and Next Steps
First, init hooks empower you to control the Odoo module lifecycle with precision. Next, they support dependency checks, data seeding, cleanup, and dynamic patches. Then, you can maintain robust modules with clear separation of concerns. Moreover, you can automate migrations and tests to deliver quality releases. Finally, you can extend this knowledge by exploring Odoo’s migration framework and advanced ORM techniques.
Furthermore, integrate init hook tests into your CI pipeline. Also, review the Odoo Developer Documentation regularly for updates. Therefore, you can master init hooks and build scalable, maintainable Odoo modules today.
Discover more from teguhteja.id
Subscribe to get the latest posts sent to your email.