Odoo 模块依赖与钩子机制完整分析报告

Complete Analysis Report on Odoo Module Dependencies and Hook Mechanisms

Executive Summary

Core issue: When a custom module depends on the contacts or company (res.company) module but no hook is written, starting Odoo causes the system to crash without any error message.

Root cause: The crash is not caused by the lack of the hook itself; rather, during installation/upgrade, the module did not correctly initialize its inheritance and extension of base models (such as res.partner or res.company), resulting in incomplete model relationships.

The effect of modifying the version number: Modifying the version number triggers Odoo's module upgrade process, re-executing model initialization and database structure synchronization, thereby "fixing" the previously incomplete initialization process.

1. In-Depth Analysis of the Odoo Module Loading Mechanism

1.1 Module Loading Process

According to loading.py core code analysis, Odoo's module loading is divided into the following key steps:

# odoo/modules/loading.py: load_modules function STEP 1: Load the base module STEP 2: Mark other modules as pending load/update STEP 3: Load marked modules (skip base) STEP 4: End migration STEP 5: Record menus, views, reports to ir_ui_menu STEP 6: Validate custom views STEP 7: Uninstall modules marked as 'to remove' STEP 8: Validate custom fields STEP 9: Call _register_hook of all models

1.2 Key Code Locations

loading.py#L175-L189:

if update_operation == 'install':
    py_module = sys.modules['odoo.addons.%s' % (module_name,)]
    pre_init = package.manifest.get('pre_init_hook')
    if pre_init:
        registry._setup_models__(env.cr, [])
 # incremental setupgetattr(py_module, pre_init)(env)
 model_names = registry.load(package)
 if update_operation:
    model_names = registry.descendants(model_names, '_inherit', '_inherits')
    models_updated |= model_names
    models_to_check -= model_names
    registry._setup_models__(env.cr, [])
 # incremental setup
    registry.init_models(env.cr, model_names, {'module': package.name}, update_operation == 'install')

II. Dependency Handling Mechanism

2.1 Module State Transitions

ir_module.py The following states are defined:

STATES = [     ('uninstallable', 'Uninstallable'),     ('uninstalled', 'Not Installed'),     ('installed', 'Installed'),     ('to upgrade', 'To be upgraded'),     ('to remove', 'To be removed'),     ('to install', 'To be installed'), ]

2.2 Dependency Verification

ir_module.py#L407-L416:

install_states = frozenset(('installed', 'to install', 'to upgrade')) defmust_install(module): states = {dep.state for dep in module.dependencies_id if dep.auto_install_required} return states <= install_states and'to install'in states and ( not module.country_ids or module.country_ids & company_countries )

Key finding: Odoo automatically handles dependencies recursively, but only checks module status, not whether model initialization has completed.

III. The Role and Timing of Hook Functions

3.1 Three Types of Hooks

1 pre_init_hook: executed before the model is loaded

2 post_init_hook: Executed after the module data is loaded

3 uninstall_hook: Executed when the module is uninstalled

3.2 Timing of Hook Invocation

loading.py#L218-L229:

if update_operation:     if update_operation == 'install':         post_init = package.manifest.get('post_init_hook')         if post_init:             getattr(py_module, post_init)(env)     elif update_operation == 'upgrade':         # validate the views that have not been checked yet         env['ir.ui.view']._validate_module_views(module_name)

3.3 Typical Uses of Hooks

After analyzing multiple official modules, common uses were found:

# Example:payment_stripe/__init__.pydefpost_init_hook(env): payment.setup_provider(env, 'stripe') # Example:timesheet_grid/__init__.pydefpre_init_hook(env): # Modify database structure before model loading env.cr.execute("ALTER TABLE...")

IV. Why Missing Hooks Cause Crashes

4.1 Root Cause Analysis

The core issue is not the lack of hooks themselves, but rather:

1 Model inheritance did not complete initialization:

When your module inherits res.partner or res.company

but did not trigger the update_operation='install' process

The model's init_models() method was not called

registry.py#L744-L772:

definit_models(self, cr: Cursor, model_names: Iterable[str], context: dict[str, typing.Any], install: bool = True): for model in models: model._auto_init() # Create/update database tables model.init() # Execute model-specific initialization env['ir.model']._reflect_models(model_names) # ← Key: reflect model metadata env['ir.model.fields']._reflect_fields(model_names) # ← Key: reflect field metadata env['ir.model.fields.selection']._reflect_selections(model_names) env['ir.model.constraint']._reflect_constraints(model_names) env['ir.model.inherit']._reflect_inherits(model_names)

1 _register_hook was not called:

loading.py#L573-L587:

# STEP 9: call _register_hook on every model# This is done *exactly once* when the registry is being loaded.for model in env.values(): model._register_hook()

The importance of _register_hook: Many modules (such as base_automation) rely on this hook to register dynamic behavior:

# base_automation/models/base_automation.pydef_register_hook(self): """Patch models that should trigger action rules"""for automation_rule inself.search([]): Model = self.env.get(automation_rule.model_name) # Dynamically modify model methods patch(Model, "create", make_create())

4.2 Reproducing the Crash Scenario

Scenario: You created a module my_module that inherits res.partner:

# my_module/models/res_partner.pyfrom odoo import models, fields classResPartner(models.Model): _inherit = 'res.partner' custom_field = fields.Char("Custom Field")

Question:

1 If the module is installed via file copy (rather than button_install)

2 or the module status in the database is installed but the installation process was not actually executed

3 custom_field will not be registered in ir.model.fields

4 Accessing this field causes an AttributeError or database constraint error

4.3 Why "No Error Was Reported"

loading.py#L157-L170 Exception handling:

try: load_openerp_module(package.name) # ... model loading logicexcept Exception: _logger.error('Failed to load registry') del cls.registries[db_name] raise

Question:

§ If the error occurs after registry.ready = True

§ Exceptions may be swallowed by Odoo's transaction rollback mechanism

§ or the error occurs when the model is first accessed (lazy loading)

V. Version Numbers and Module Update Mechanism

5.1 Version Number Comparison Logic

ir_module.py#L790:

if parse_version(terp.get('version', default_version)) > parse_version(mod.latest_version or default_version): res[0] += 1# Mark as needing update

5.2 Naming Confusion of Version Number Fields

Note: Odoo's field naming is very confusing!

ir_module.py#L276-L281:

# Note: The field names are incorrect!# installed_version refers to the latest version (on disk)# latest_version refers to the installed version (in the database)# published_version refers to the version available in the repository installed_version = fields.Char('Latest Version', compute='_get_latest_version') latest_version = fields.Char('Installed Version', readonly=True) published_version = fields.Char('Published Version', readonly=True)

5.3 Process Triggered by Version Number Modification

When you modify the version number in __manifest__.py:

'version': '1.0.0.1', # Changed from 1.0.0.0 to 1.0.0.1

When running odoo-bin -u my_module:

1 update_list() detected a version change

2 Module status from installed → to upgrade

3 Trigger the complete upgrade process:

§ Call the pre-migration script (if it exists)

§ Reload module (load_openerp_module)

§ Call registry._setup_models__()

§ Call registry.init_models()

§ Call the post migration script (if it exists)

4 Re-register all hooks:

loading.py#L573-L587:

for model in env.values(): model._register_hook()

VI. Root Causes and Solutions

6.1 Root Cause Summary

The problem is not "there are no hooks," but rather:

1 The module did not go through the normal installation process

Directly copy the module files to the addons directory

Manually modify the ir_module_module table in the database

Used an incomplete installation script

2 Model initialization not completed

init_models() was not called

Field is not registered in ir.model.fields

Constraint not registered in ir.model.constraint

3 _register_hook was not executed

Dynamic behavior not registered

The dependent module's hook was not triggered

6.2 Why Modifying the Version Number "Can Solve It"

Modifying the version number and running -u module_name will:

1 Enforce the complete upgrade process

2 Reinitialize all models

3 Re-register all hooks

4 Synchronize database structure

This is a "brute-force fix", which in reality amounts to going through the correct installation process all over again.

VII. Correct Development Practices

7.1 The correct way to install the module

Never:

 ❌ Directly copy the module to addons and restart

❌ Manually modifying the ir_module_module table

❌ Using the --load parameter to load an uninstalled module

Should use:

# Method 1: Install via UI# Apps → Update Apps List → Find the module → Click Install# Method 2: Install via command line odoo-bin -i my_module -d mydb # Method 3: Upgrade via command line odoo-bin -u my_module -d mydb

7.2 When do you need to write a hook

Situations where post_init_hook must be written:

1 Initial data needs to be created:

defpost_init_hook(env): # Create default configuration record env['my.config'].create({ 'name': 'Default', 'value': 100, })

1 Need to modify existing data:

defpost_init_hook(env): # Update the new field for all partners partners = env['res.partner'].search([]) partners.write({'new_field': 'default_value'})

1 System parameters need to be set:

defpost_init_hook(env):     env['ir.config_parameter'].set_param('my_module.enabled', 'True')

Situations where pre_init_hook must be written:

1 The database structure needs to be modified before the model is loaded:

defpre_init_hook(env): # Rename column (to avoid field migration issues)if env.cr.column_exists('res_partner', 'old_field'): env.cr.execute("ALTER TABLE res_partner RENAME COLUMN old_field TO new_field")

1 Data needs to be cleaned before model loading:

defpre_init_hook(env): # Delete data that may cause constraint conflicts env.cr.execute("DELETE FROM my_table WHERE invalid_condition")

7.3 Correct Approach for Depending on res.partner or res.company

Example: Correct module structure

# __manifest__.py {     'name': 'My Module',     'version': '1.0.0',     'depends': ['base', 'contacts'],  # ← 明确声明依赖'data': [         'security/ir.model.access.csv',         'views/res_partner_views.xml',     ],     'post_init_hook': 'post_init_hook',  # ← 可选,仅在需要时添加 }  # __init__.pyfrom . import models  defpost_init_hook(env):     """仅在确实需要初始化数据时才写"""# 例如:为所有现有合作伙伴设置默认值     partners = env['res.partner'].search([('my_custom_field', '=', False)])     partners.write({'my_custom_field': 'default'})  # models/res_partner.pyfrom odoo import models, fields  classResPartner(models.Model):     _inherit = 'res.partner'      my_custom_field = fields.Char(         string="My Field",         help="This field extends res.partner",         default='default',  # ← 为新记录设置默认值     )      def_register_hook(self):         """仅在需要动态修改行为时才覆盖"""super()._register_hook()         # 你的自定义钩子逻辑

VIII. Common Errors and Diagnosis

8.1 Error Scenario 1: Field Does Not Exist

Symptoms:

AttributeError: 'res.partner' object has no attribute 'my_custom_field'

Reason:

The module file exists, but was not installed via button_install or -i

This field is not registered in ir.model.fields

Diagnosis:

-- Check module statusSELECT name, state, latest_version FROM ir_module_module WHERE name ='my_module'; -- Check whether the field is registeredSELECT name, model, state FROM ir_model_fields WHERE model ='res.partner'AND name ='my_custom_field';

Fix:

odoo-bin -u my_module -d mydb

8.2 Error Scenario 2: Database Constraint Error

Symptoms:

psycopg2.errors.NotNullViolation: null value in column "my_custom_field" violates not-null constraint

Reason:

field defines required=True

but did not set values for existing records in post_init_hook

Fix:

defpost_init_hook(env): # Method 1: bulk set default value env.cr.execute(""" UPDATE res_partner SET my_custom_field = 'default' WHERE my_custom_field IS NULL """) # Method 2: use ORM (safer but slower) partners = env['res.partner'].search([('my_custom_field', '=', False)]) partners.write({'my_custom_field': 'default'})

8.3 Error Scenario 3: _register_hook not executed

Symptoms:

Features that rely on dynamic behavior do not work

For example: rules in base_automation do not trigger

Reason:

Module did not pass the complete installation process

Attempt to register hook when registry.ready is False

Diagnosis:

# Add logging in the modeldef_register_hook(self): _logger.info("=== _register_hook called for %s ===", self._name) super()._register_hook()

Fix:

Ensure the use of -i or -u to install/upgrade modules

Check whether loading of certain modules is disabled in odoo.conf

IX. Summary of Best Practices

9.1 Development Phase

1 Always use -u to update modules:

odoo-bin -u my_module -d dev_db --dev=all

1 Increment the version number after each model modification:

# From 'version': '1.0.0.1', # change to 'version': '1.0.0.2',

1 Use --dev=all to enable automatic reload:

odoo-bin --dev=all

9.2 Production Deployment

1 Use migration scripts instead of hooks:

# migrations/1.0.0.2/pre-migrate.pydefmigrate(cr, version): # Execute before the model is loadedpass# migrations/1.0.0.2/post-migrate.pydefmigrate(cr, version): # Execute after the model is loadedpass

1 Enforce the use of -u in CI/CD:

# deploy.sh odoo-bin -u my_module --stop-after-init -d prod_db

1 Record version change log:

# __manifest__.py'version': '1.0.0.2', # CHANGELOG.md## 1.0.0.2 (2025-01-01) - Added custom_field to res.partner - Fixed initialization issue

10. Conclusion

10.1 Key Takeaways

1 Hooks are not required: In most cases, hooks are not needed; simply install the module correctly.

2 Modifying the version number is not "avoiding errors": it is a means of triggering the complete upgrade process; in essence, it re-executes the correct initialization.

3 The root problem is that the installation process is incomplete:

Must be installed via -i, -u, or UI

Cannot skip init_models() and _register_hook()

4 "Crash with no error message" is usually a delayed error:

Errors may only occur when the model is first accessed

Use --log-level=debug to view detailed logs

10.2 Recommended Practices

For simple modules (only adding fields):

# __manifest__.py { 'name': 'Simple Module', 'version': '1.0.0', 'depends': ['base', 'contacts'], 'data': ['views/views.xml'], # No hooks needed! }

For complex modules (that require data initialization):

# __manifest__.py {     'name': 'Complex Module',     'version': '1.0.0',     'depends': ['base', 'contacts'],     'data': [         'security/ir.model.access.csv',         'data/initial_data.xml',  # ← Prefer using XML data files'views/views.xml',     ],     'post_init_hook': 'post_init_hook',  # ← Use only when XML cannot accomplish it }  defpost_init_hook(env):     """Only for initialization that must be completed in code"""     _logger.info("Initializing %s", __name__)     # Your initialization code

10.3 Troubleshooting Process

Checklist for when encountering a "crash without errors":

# 1. Check module status SELECT name, state, latest_version FROM ir_module_module WHERE name = 'my_module'; # 2. Check whether model is registered SELECT model FROM ir_model WHERE model = 'res.partner'; # 3. Check whether fields are registered SELECT name, model FROM ir_model_fields WHERE model = 'res.partner' AND module = 'my_module'; # 4. Check whether constraints are registered SELECT name, model FROM ir_model_constraint WHERE model = 'res.partner' AND module = 'my_module'; # 5. Restart in debug mode odoo-bin -d mydb --log-level=debug --dev=all # 6. Force reinstall odoo-bin -d mydb -u my_module --stop-after-init # 7. View PostgreSQL logs tail -f /var/log/postgresql/postgresql-*.log

Appendix: Reference Code Location

A. Module Loading

§ odoo/modules/loading.py#L342-L625 - load_modules() function

§ odoo/modules/loading.py#L157-L250 - load_module_graph() function

§ odoo/modules/module.py#L492-L522 - load_openerp_module() function

B. Registry Management

§ odoo/orm/registry.py#L140-L235 - Registry.new() method

§ odoo/orm/registry.py#L367-L398 - Registry.load() method

§ odoo/orm/registry.py#L399-L540 - Registry._setup_models__() method

§ odoo/orm/registry.py#L730-L778 - Registry.init_models() method

C. Module Management

§ odoo/addons/base/models/ir_module.py#L400-L468 - button_install() method

§ odoo/addons/base/models/ir_module.py#L692-L740 - button_upgrade() method

§ odoo/addons/base/models/ir_module.py#L760-L830 - update_list() method

D. Migration Management

§ odoo/modules/migration.py#L59-L220 - MigrationManager class

§ odoo/modules/migration.py#L229-L268 - exec_script() function

Report Date: December 30, 2025

Odoo Version: 19.0 Enterprise Edition

Analysis object: odoo19ee

Final Answer

Q: Can modifying the version number avoid the error?

Answer: It is not "avoiding", but "fixing".

Modifying the version number triggers the complete module upgrade process, which will:

1 Reload module code

2 Reinitialize all models (init_models)

3 Re-register all hooks (_register_hook)

4 Synchronize database structure and metadata

Essentially, this is compensating for installation steps that were not completed previously.

The correct approach is: use the -i or -u flags from the start to properly install the module, rather than relying on version number changes to "fix" the problem.

If you must rely on version number changes: it means your deployment process has problems, and you should check:

Should the file be copied directly rather than installed using -i?

Has the module status in the database been manually modified?

Did the CI/CD script correctly execute the -u command?

关于我们

​我们致力于帮助中小企业实现数字化转型,我们的团队由一群充满激情和创新思维的专业人士组成,他们具备丰富的行业经验和技术专长。

扫一扫获取顾问以及手册

归档
Sign in to leave a comment
Odoo 19 Sales Module Business Logic Deep Analysis: Process, Architecture, and System Collaboration Analysis