odoo每次启动的时候都会加载模块,加载模块的过程就是调用load_modules 函数
在函数位于 odoo\modules\loading.py
代码中注释也写的很清楚,共分了9个步骤,其实是8个步骤。
这个函数的作用是为一个新创建的注册表对象加载模块,这是Registry.new()的一部分,不应该用在其他地方。
def load_modules(registry, force_demo=False, status=None, update_module=False):
""" Load the modules for a registry object that has just been created. This
function is part of Registry.new() and should not be used anywhere else.
"""
initialize_sys_path()
force = []
if force_demo:
force.append('demo')
models_to_check = set()
with registry.cursor() as cr:
# prevent endless wait for locks on schema changes (during online
# installs) if a concurrent transaction has accessed the table;
# connection settings are automatically reset when the connection is
# borrowed from the pool
cr.execute("SET SESSION lock_timeout = '15s'")
if not odoo.modules.db.is_initialized(cr):
if not update_module:
_logger.error("Database %s not initialized, you can force it with `-i base`", cr.dbname)
return
_logger.info("init db")
odoo.modules.db.initialize(cr)
update_module = True # process auto-installed modules
tools.config["init"]["all"] = 1
if not tools.config['without_demo']:
tools.config["demo"]['all'] = 1
if 'base' in tools.config['update'] or 'all' in tools.config['update']:
cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
# STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
graph = odoo.modules.graph.Graph()
graph.add_module(cr, 'base', force)
if not graph:
_logger.critical('module base cannot be loaded! (hint: verify addons-path)')
raise ImportError('Module `base` cannot be loaded! (hint: verify addons-path)')
if update_module and odoo.tools.table_exists(cr, 'ir_model_fields'):
# determine the fields which are currently translated in the database
cr.execute("SELECT model || '.' || name FROM ir_model_fields WHERE translate IS TRUE")
registry._database_translated_fields = {row[0] for row in cr.fetchall()}
# processed_modules: for cleanup step after install
# loaded_modules: to avoid double loading
report = registry._assertion_report
loaded_modules, processed_modules = load_module_graph(
cr, graph, status, perform_checks=update_module,
report=report, models_to_check=models_to_check)
load_lang = tools.config.pop('load_language')
if load_lang or update_module:
# some base models are used below, so make sure they are set up
registry.setup_models(cr)
if load_lang:
for lang in load_lang.split(','):
tools.load_language(cr, lang)
# STEP 2: Mark other modules to be loaded/updated
if update_module:
env = api.Environment(cr, SUPERUSER_ID, {})
Module = env['ir.module.module']
_logger.info('updating modules list')
Module.update_list()
_check_module_names(cr, itertools.chain(tools.config['init'], tools.config['update']))
module_names = [k for k, v in tools.config['init'].items() if v]
if module_names:
modules = Module.search([('state', '=', 'uninstalled'), ('name', 'in', module_names)])
if modules:
modules.button_install()
module_names = [k for k, v in tools.config['update'].items() if v]
if module_names:
modules = Module.search([('state', 'in', ('installed', 'to upgrade')), ('name', 'in', module_names)])
if modules:
modules.button_upgrade()
env.flush_all()
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
Module.invalidate_model(['state'])
# STEP 3: Load marked modules (skipping base which was done in STEP 1)
# IMPORTANT: this is done in two parts, first loading all installed or
# partially installed modules (i.e. installed/to upgrade), to
# offer a consistent system to the second part: installing
# newly selected modules.
# We include the modules 'to remove' in the first step, because
# they are part of the "currently installed" modules. They will
# be dropped in STEP 6 later, before restarting the loading
# process.
# IMPORTANT 2: We have to loop here until all relevant modules have been
# processed, because in some rare cases the dependencies have
# changed, and modules that depend on an uninstalled module
# will not be processed on the first pass.
# It's especially useful for migrations.
previously_processed = -1
while previously_processed < len(processed_modules):
previously_processed = len(processed_modules)
processed_modules += load_marked_modules(cr, graph,
['installed', 'to upgrade', 'to remove'],
force, status, report, loaded_modules, update_module, models_to_check)
if update_module:
processed_modules += load_marked_modules(cr, graph,
['to install'], force, status, report,
loaded_modules, update_module, models_to_check)
if update_module:
# set up the registry without the patch for translated fields
database_translated_fields = registry._database_translated_fields
registry._database_translated_fields = ()
registry.setup_models(cr)
# determine which translated fields should no longer be translated,
# and make their model fix the database schema
models_to_untranslate = set()
for full_name in database_translated_fields:
model_name, field_name = full_name.rsplit('.', 1)
if model_name in registry:
field = registry[model_name]._fields.get(field_name)
if field and not field.translate:
_logger.debug("Making field %s non-translated", field)
models_to_untranslate.add(model_name)
registry.init_models(cr, list(models_to_untranslate), {'models_to_check': True})
registry.loaded = True
registry.setup_models(cr)
# check that all installed modules have been loaded by the registry
env = api.Environment(cr, SUPERUSER_ID, {})
Module = env['ir.module.module']
modules = Module.search(Module._get_modules_to_load_domain(), order='name')
missing = [name for name in modules.mapped('name') if name not in graph]
if missing:
_logger.error("Some modules are not loaded, some dependencies or manifest may be missing: %s", missing)
# STEP 3.5: execute migration end-scripts
migrations = odoo.modules.migration.MigrationManager(cr, graph)
for package in graph:
migrations.migrate_module(package, 'end')
# check that new module dependencies have been properly installed after a migration/upgrade
cr.execute("SELECT name from ir_module_module WHERE state IN ('to install', 'to upgrade')")
module_list = [name for (name,) in cr.fetchall()]
if module_list:
_logger.error("Some modules have inconsistent states, some dependencies may be missing: %s", sorted(module_list))
# STEP 3.6: apply remaining constraints in case of an upgrade
registry.finalize_constraints()
# STEP 4: Finish and cleanup installations
if processed_modules:
env = api.Environment(cr, SUPERUSER_ID, {})
cr.execute("SELECT model from ir_model")
for (model,) in cr.fetchall():
if model in registry:
env[model]._check_removed_columns(log=True)
elif _logger.isEnabledFor(logging.INFO): # more an info that a warning...
_logger.runbot("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
# Cleanup orphan records
env['ir.model.data']._process_end(processed_modules)
env.flush_all()
for kind in ('init', 'demo', 'update'):
tools.config[kind] = {}
# STEP 5: Uninstall modules to remove
if update_module:
# Remove records referenced from ir_model_data for modules to be
# removed (and removed the references from ir_model_data).
cr.execute("SELECT name, id FROM ir_module_module WHERE state=%s", ('to remove',))
modules_to_remove = dict(cr.fetchall())
if modules_to_remove:
env = api.Environment(cr, SUPERUSER_ID, {})
pkgs = reversed([p for p in graph if p.name in modules_to_remove])
for pkg in pkgs:
uninstall_hook = pkg.info.get('uninstall_hook')
if uninstall_hook:
py_module = sys.modules['odoo.addons.%s' % (pkg.name,)]
getattr(py_module, uninstall_hook)(cr, registry)
env.flush_all()
Module = env['ir.module.module']
Module.browse(modules_to_remove.values()).module_uninstall()
# Recursive reload, should only happen once, because there should be no
# modules to remove next time
cr.commit()
_logger.info('Reloading registry once more after uninstalling modules')
registry = odoo.modules.registry.Registry.new(
cr.dbname, force_demo, status, update_module
)
cr.reset()
registry.check_tables_exist(cr)
cr.commit()
return registry
# STEP 5.5: Verify extended fields on every model
# This will fix the schema of all models in a situation such as:
# - module A is loaded and defines model M;
# - module B is installed/upgraded and extends model M;
# - module C is loaded and extends model M;
# - module B and C depend on A but not on each other;
# The changes introduced by module C are not taken into account by the upgrade of B.
if models_to_check:
registry.init_models(cr, list(models_to_check), {'models_to_check': True})
# STEP 6: verify custom views on every model
if update_module:
env = api.Environment(cr, SUPERUSER_ID, {})
env['res.groups']._update_user_groups_view()
View = env['ir.ui.view']
for model in registry:
try:
View._validate_custom_views(model)
except Exception as e:
_logger.warning('invalid custom view(s) for model %s: %s', model, tools.ustr(e))
if report.wasSuccessful():
_logger.info('Modules loaded.')
else:
_logger.error('At least one test failed when loading the modules.')
# STEP 8: save installed/updated modules for post-install tests and _register_hook
registry.updated_modules += processed_modules
# STEP 9: call _register_hook on every model
# This is done *exactly once* when the registry is being loaded. See the
# management of those hooks in `Registry.setup_models`: all the calls to
# setup_models() done here do not mess up with hooks, as registry.ready
# is False.
env = api.Environment(cr, SUPERUSER_ID, {})
for model in env.values():
model._register_hook()
env.flush_all()
这里判断数据库有没有被初始化,如果没有,就安装base模块
initialize_sys_path()
force = []
if force_demo:
force.append('demo')
models_to_check = set()
with registry.cursor() as cr:
# prevent endless wait for locks on schema changes (during online
# installs) if a concurrent transaction has accessed the table;
# connection settings are automatically reset when the connection is
# borrowed from the pool
cr.execute("SET SESSION lock_timeout = '15s'")
if not odoo.modules.db.is_initialized(cr):
if not update_module:
_logger.error("Database %s not initialized, you can force it with `-i base`", cr.dbname)
return
_logger.info("init db")
odoo.modules.db.initialize(cr)
update_module = True # process auto-installed modules
tools.config["init"]["all"] = 1
if not tools.config['without_demo']:
tools.config["demo"]['all'] = 1
if 'base' in tools.config['update'] or 'all' in tools.config['update']:
cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
第一步就是加载base模块, 最后还加载了其他模块的语言包
SELECT model || '.' || name FROM ir_model_fields WHERE translate IS TRUE #原来pg使用|| 来拼接字符串的。
registry._database_translated_fields = {row[0] for row in cr.fetchall()} # 这个写法也很有意思,简洁
# STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
graph = odoo.modules.graph.Graph()
graph.add_module(cr, 'base', force)
if not graph:
_logger.critical('module base cannot be loaded! (hint: verify addons-path)')
raise ImportError('Module `base` cannot be loaded! (hint: verify addons-path)')
if update_module and odoo.tools.table_exists(cr, 'ir_model_fields'):
# determine the fields which are currently translated in the database
cr.execute("SELECT model || '.' || name FROM ir_model_fields WHERE translate IS TRUE")
registry._database_translated_fields = {row[0] for row in cr.fetchall()}
# processed_modules: for cleanup step after install
# loaded_modules: to avoid double loading
report = registry._assertion_report
loaded_modules, processed_modules = load_module_graph(
cr, graph, status, perform_checks=update_module,
report=report, models_to_check=models_to_check)
load_lang = tools.config.pop('load_language')
if load_lang or update_module:
# some base models are used below, so make sure they are set up
registry.setup_models(cr)
if load_lang:
for lang in load_lang.split(','):
tools.load_language(cr, lang)
控制台输出
2023-11-04 07:58:50,981 1340 INFO ? odoo.service.server: HTTP service (werkzeug) running on LAPTOP-AV3CF7SO:8069
2023-11-04 07:58:50,987 1340 INFO odoo2 odoo.modules.loading: loading 1 modules...
2023-11-04 07:58:51,008 1340 INFO odoo2 odoo.modules.loading: 1 modules loaded in 0.02s, 0 queries (+0 extra)
2023-11-04 07:58:51,154 1340 INFO odoo2 odoo.addons.base.models.ir_module: module base: loading translation file zh_CN for language zh_CN
2023-11-04 07:58:52,077 1340 INFO odoo2 odoo.addons.base.models.ir_module: module web: loading translation file zh_CN for language zh_CN
2023-11-04 07:58:52,256 1340 INFO odoo2 odoo.addons.base.models.ir_module: module base_setup: loading translation file zh_CN for language zh_CN
2023-11-04 07:58:52,282 1340 INFO odoo2 odoo.addons.base.models.ir_module: module bus: loading translation file zh_CN for language zh_CN
2023-11-04 07:58:52,295 1340 INFO odoo2 odoo.addons.base.models.ir_module: module web_tour: loading translation file zh_CN for language zh_CN
2023-11-04 07:58:52,304 1340 INFO odoo2 odoo.addons.base.models.ir_module: module mail: loading translation file zh_CN for language zh_CN
中间省略N行....
sql_constraint:cf.report.define.field
2023-11-04 07:58:54,087 1340 INFO odoo2 odoo.tools.translate: Skipped deprecated occurrence sql_constraint:cf.report.define
2023-11-04 07:58:54,087 1340 INFO odoo2 odoo.tools.translate: Skipped deprecated occurrence selection:cf.report.define,state
2023-11-04 07:58:54,094 1340 INFO odoo2 odoo.addons.base.models.ir_module: module estate: no translation for language zh_CN
2023-11-04 07:58:54,102 1340 INFO odoo2 odoo.addons.base.models.ir_module: module hx_base: no translation for language zh_CN
2023-11-04 07:58:54,115 1340 INFO odoo2 odoo.addons.base.models.ir_module: module hx_chp: no translation for language zh_CN
2023-11-04 07:58:54,122 1340 INFO odoo2 odoo.addons.base.models.ir_module: module hx_gift: no translation for language zh_CN
2023-11-04 07:58:54,129 1340 INFO odoo2 odoo.addons.base.models.ir_module: module hx_jixiao: no translation for language zh_CN
2023-11-04 07:58:54,136 1340 INFO odoo2 odoo.addons.base.models.ir_module: module hx_tsl: no translation for language zh_CN
2023-11-04 07:58:54,143 1340 INFO odoo2 odoo.addons.base.models.ir_module: module auth_totp: loading translation file zh_CN for
# STEP 2: Mark other modules to be loaded/updated
if update_module:
env = api.Environment(cr, SUPERUSER_ID, {})
Module = env['ir.module.module']
_logger.info('updating modules list')
Module.update_list()
_check_module_names(cr, itertools.chain(tools.config['init'], tools.config['update']))
module_names = [k for k, v in tools.config['init'].items() if v]
if module_names:
modules = Module.search([('state', '=', 'uninstalled'), ('name', 'in', module_names)])
if modules:
modules.button_install()
module_names = [k for k, v in tools.config['update'].items() if v]
if module_names:
modules = Module.search([('state', 'in', ('installed', 'to upgrade')), ('name', 'in', module_names)])
if modules:
modules.button_upgrade()
env.flush_all()
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
Module.invalidate_model(['state'])
如果没有,这一步就跳过了
1、分两个步骤完成,第一步加载所有已经安装的或者部分安装(需要升级),为第二步提供一个一致性的系统。第二步是安装新的模块。
我们在第一步中考虑了to_remove 的模块,因为他们是“已安装”模块的一部分, 他们将在step 6 去处理。
2、这里不得不循环处理,因为要处理模块之间的依赖关系
# STEP 3: Load marked modules (skipping base which was done in STEP 1)
# IMPORTANT: this is done in two parts, first loading all installed or
# partially installed modules (i.e. installed/to upgrade), to
# offer a consistent system to the second part: installing
# newly selected modules.
# We include the modules 'to remove' in the first step, because
# they are part of the "currently installed" modules. They will
# be dropped in STEP 6 later, before restarting the loading
# process.
# IMPORTANT 2: We have to loop here until all relevant modules have been
# processed, because in some rare cases the dependencies have
# changed, and modules that depend on an uninstalled module
# will not be processed on the first pass.
# It's especially useful for migrations.
previously_processed = -1
# 循环加载处理相关模块
while previously_processed < len(processed_modules):
previously_processed = len(processed_modules)
processed_modules += load_marked_modules(cr, graph,
['installed', 'to upgrade', 'to remove'],
force, status, report, loaded_modules, update_module, models_to_check)
if update_module:
processed_modules += load_marked_modules(cr, graph,
['to install'], force, status, report,
loaded_modules, update_module, models_to_check)
# 处理需要升级的模块
if update_module:
# set up the registry without the patch for translated fields
database_translated_fields = registry._database_translated_fields
registry._database_translated_fields = ()
registry.setup_models(cr)
# determine which translated fields should no longer be translated,
# and make their model fix the database schema
models_to_untranslate = set()
for full_name in database_translated_fields:
model_name, field_name = full_name.rsplit('.', 1)
if model_name in registry:
field = registry[model_name]._fields.get(field_name)
if field and not field.translate:
_logger.debug("Making field %s non-translated", field)
models_to_untranslate.add(model_name)
registry.init_models(cr, list(models_to_untranslate), {'models_to_check': True})
registry.loaded = True
registry.setup_models(cr)
# check that all installed modules have been loaded by the registry
env = api.Environment(cr, SUPERUSER_ID, {})
Module = env['ir.module.module']
modules = Module.search(Module._get_modules_to_load_domain(), order='name')
missing = [name for name in modules.mapped('name') if name not in graph]
if missing:
_logger.error("Some modules are not loaded, some dependencies or manifest may be missing: %s", missing)
# STEP 3.5: execute migration end-scripts
migrations = odoo.modules.migration.MigrationManager(cr, graph)
for package in graph:
migrations.migrate_module(package, 'end')
# check that new module dependencies have been properly installed after a migration/upgrade
cr.execute("SELECT name from ir_module_module WHERE state IN ('to install', 'to upgrade')")
module_list = [name for (name,) in cr.fetchall()]
if module_list:
_logger.error("Some modules have inconsistent states, some dependencies may be missing: %s", sorted(module_list))
# STEP 3.6: apply remaining constraints in case of an upgrade
registry.finalize_constraints()
控制台输出:
2023-11-04 08:16:23,488 11076 INFO odoo2 odoo.modules.loading: loading 54 modules...
2023-11-04 08:16:23,882 11076 WARNING odoo2 odoo.api.create: The model odoo.addons.hx_chp.models.change_point is not overriding the create method
2023-11-04 08:16:23,944 11076 WARNING odoo2 odoo.api.create: The model odoo.addons.hx_tsl.models.hx_tsl_info is not overriding the create method in batch
2023-11-04 08:16:24,292 11076 WARNING odoo2 odoo.api.create: The model odoo.addons.cfprint.models.cfprint_server_user_map is not overriding the create method in batch
2023-11-04 08:16:25,047 11076 WARNING odoo2 odoo.api.create: The model odoo.addons.cf_report_designer.models.data_model_3 is not overriding the create method in batch
2023-11-04 08:16:25,069 11076 WARNING odoo2 odoo.api.create: The model odoo.addons.cf_report_designer.models.data_model_4 is not overriding the create method in batch
2023-11-04 08:16:25,071 11076 WARNING odoo2 odoo.api.create: The model odoo.addons.cf_report_designer.models.data_model_4 is not overriding the create method in batch
2023-11-04 08:16:25,416 11076 INFO odoo2 odoo.modules.loading: 54 modules loaded in 1.93s, 0 queries (+0 extra)
基本就是一些善后过程,正常情况下不会执行
# STEP 4: Finish and cleanup installations
if processed_modules:
env = api.Environment(cr, SUPERUSER_ID, {})
cr.execute("SELECT model from ir_model")
for (model,) in cr.fetchall():
if model in registry:
env[model]._check_removed_columns(log=True)
elif _logger.isEnabledFor(logging.INFO): # more an info that a warning...
_logger.runbot("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
# Cleanup orphan records
env['ir.model.data']._process_end(processed_modules)
env.flush_all()
for kind in ('init', 'demo', 'update'):
tools.config[kind] = {}
SELECT name, id FROM ir_module_module WHERE state=%s", ('to remove',)
如果配置了uninstall_hook,要在这里执行。 然后调用module_uninstall()方法来卸载
Module.browse(modules_to_remove.values()).module_uninstall()
卸载完成之后,刷新注册表。
5.5 是要处理一些模型之间的依赖关系。
# STEP 5: Uninstall modules to remove
if update_module:
# Remove records referenced from ir_model_data for modules to be
# removed (and removed the references from ir_model_data).
cr.execute("SELECT name, id FROM ir_module_module WHERE state=%s", ('to remove',))
modules_to_remove = dict(cr.fetchall())
if modules_to_remove:
env = api.Environment(cr, SUPERUSER_ID, {})
pkgs = reversed([p for p in graph if p.name in modules_to_remove])
for pkg in pkgs:
uninstall_hook = pkg.info.get('uninstall_hook')
if uninstall_hook:
py_module = sys.modules['odoo.addons.%s' % (pkg.name,)]
getattr(py_module, uninstall_hook)(cr, registry)
env.flush_all()
Module = env['ir.module.module']
Module.browse(modules_to_remove.values()).module_uninstall()
# Recursive reload, should only happen once, because there should be no
# modules to remove next time
cr.commit()
_logger.info('Reloading registry once more after uninstalling modules')
registry = odoo.modules.registry.Registry.new(
cr.dbname, force_demo, status, update_module
)
cr.reset()
registry.check_tables_exist(cr)
cr.commit()
return registry
# STEP 5.5: Verify extended fields on every model
# This will fix the schema of all models in a situation such as:
# - module A is loaded and defines model M;
# - module B is installed/upgraded and extends model M;
# - module C is loaded and extends model M;
# - module B and C depend on A but not on each other;
# The changes introduced by module C are not taken into account by the upgrade of B.
if models_to_check:
registry.init_models(cr, list(models_to_check), {'models_to_check': True})
# STEP 6: verify custom views on every model
if update_module:
env = api.Environment(cr, SUPERUSER_ID, {})
env['res.groups']._update_user_groups_view()
View = env['ir.ui.view']
for model in registry:
try:
View._validate_custom_views(model)
except Exception as e:
_logger.warning('invalid custom view(s) for model %s: %s', model, tools.ustr(e))
if report.wasSuccessful():
_logger.info('Modules loaded.')
else:
_logger.error('At least one test failed when loading the modules.')
控制台输出
2023-11-04 08:35:43,841 11076 INFO odoo2 odoo.modules.loading: Modules loaded.
为了post-install测试 和调用_register_hook, step7 哪去了????
# STEP 8: save installed/updated modules for post-install tests and _register_hook
registry.updated_modules += processed_modules
对每个model模型调用_register_hook,每次启动的时候都会调用,能干什么呢?
# STEP 9: call _register_hook on every model
# This is done *exactly once* when the registry is being loaded. See the
# management of those hooks in `Registry.setup_models`: all the calls to
# setup_models() done here do not mess up with hooks, as registry.ready
# is False.
env = api.Environment(cr, SUPERUSER_ID, {})
for model in env.values():
model._register_hook()
env.flush_all()
models.py
def _register_hook(self):
""" stuff to do right after the registry is built """
def _unregister_hook(self):
""" Clean up what `~._register_hook` has done. """
_register_hook 有什么用?
可以在模块加载完之后动态修改模型的一些属性值。
比如修改_rec_name
def _register_hook(self):
setattr(type(self),'_rec_name', 'myname')
安装模块的时候也会执行load_modules函数重新加载所有的模块,如果你希望你的逻辑只有在启动的时候执行而不是安装模块的时候执行,在context中有个变量可以区分。
哪个变量?