Skip to content
Home » Init Hooks in Odoo

Init Hooks in Odoo

  • Odoo
init hooks odoo

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

  1. Dependency Checks: Halt installation when prerequisites are missing.
  2. Schema Preparation: Manually create or alter tables.
  3. 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

  1. Data Seeding: Create default records.
  2. Data Migration: Migrate values from old models.
  3. 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

  1. Orphan Data Cleanup: Remove external logs.
  2. Archive Records: Move records to an archive table.
  3. 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

  1. Monkey Patching: Override methods in core or other addons.
  2. Global Configuration: Set global defaults.
  3. 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.

Leave a Reply

WP Twitter Auto Publish Powered By : XYZScripts.com