Registry
Registry是odoo中用来处理每个数据库中模型名称和模型类对应关系的工具,每个数据库只有一个Registry的实例。
类属性
Registry有几个比较重要的类属性
registries
registries负责维护数据库与registry实例的映射关系,也就说每个数据库都可以通过registries[dbname]的方式获取本数据库对应的registry实例。
registries的内部实现使用了LRU(最近最少使用)机制,可以通过配置文件的registry_lru_size参数来指定队列的长度。由于Windows不支持支持虚拟内存上限,因此Windows上的LRU队列长度是个固定值42。而对于Linux系统,默认分配给每个registry实例的内存为15M,所以队列的长度由虚拟的存储上限除以15计算而来。
odoo默认的虚拟内存上限是2048M,所以默认的LRU的队列长度为 2048/15 = 136
类方法new
Registry的new方法用来创建并返回给出的数据库名称的Registry实例。
@classmethod
def new(cls, db_name, force_demo=False, status=None, update_module=False):
""" Create and return a new registry for the given database name. """
with cls._lock:
with odoo.api.Environment.manage():
registry = object.__new__(cls)
registry.init(db_name)
# Initializing a registry will call general code which will in
# turn call Registry() to obtain the registry being initialized.
# Make it available in the registries dictionary then remove it
# if an exception is raised.
cls.delete(db_name)
cls.registries[db_name] = registry
try:
registry.setup_signaling()
# This should be a method on Registry
try:
odoo.modules.load_modules(registry._db, force_demo, status, update_module)
except Exception:
odoo.modules.reset_modules_state(db_name)
raise
except Exception:
_logger.error('Failed to load registry')
del cls.registries[db_name]
raise
# load_modules() above can replace the registry by calling
# indirectly new() again (when modules have to be uninstalled).
# Yeah, crazy.
registry = cls.registries[db_name]
registry._init = False
registry.ready = True
registry.registry_invalidated = bool(update_module)
return registry
从代码中可以看出,Registry在new方法中完成了实例registry与数据库dbname的映射,然后调用了moduels模块的load_modules方法将模块注册到registry关联的db中。
实例属性
registries和new方法主要用来处理dbname和regstry的关系,而registry的任务则是处理model和modelclass的关系,下面我们就来看看它的实例属性和方法。
- registry: 使用实例属性models来维护model和model实例的对应关系。
- db_name:来记录当前数据库名称。
- loaded: 标识是否已经加载所有模块。
- ready: 标识是否一切准备就绪。
Registry中有个load方法用来加载给出的模块中的所有改动过的模型的名称,然后再调用模型(model)的_build_model方法,将模型注册到registry中。
def load(self, cr, module):
""" Load a given module in the registry, and return the names of the
modified models.
At the Python level, the modules are already loaded, but not yet on a
per-registry level. This method populates a registry with the given
modules, i.e. it instanciates all the classes of a the given module
and registers them in the registry.
"""
from .. import models
# clear cache to ensure consistency, but do not signal it
self.__cache.clear()
lazy_property.reset_all(self)
# Instantiate registered classes (via the MetaModel automatic discovery
# or via explicit constructor call), and add them to the pool.
model_names = []
for cls in models.MetaModel.module_to_models.get(module.name, []):
# models register themselves in self.models
model = cls._build_model(self, cr)
model_names.append(model._name)
return self.descendants(model_names, '_inherit', '_inherits')
modules的加载
前面提到,Registry在new方法中完成了registry与数据库名称的映射,然后向该数据库中加载了模块。那么模块是如何加载的呢?
在modules的load_modules方法内部,会首选确认当前数据库是否进行了初始化,如果没有则会提示用户进行初始化操作。之后,系统会检查命令参数中是否指定了要更新全部的模块,如果是,则更新全部的模块。(-u all)。
实际上,更新全部模块的效果跟更新base模块的效果一样,其本质都是将ir_module_module表中base模块的状态设置为to upgrade。
在odoo中base模块是其他模块的依赖模块,也是加载过程中第一个被加载的模块。加载完base模块之后开始加载其他模块,如果有需要更新的模块,则进行升级更新。
所有模块加载完成以后,调用registry的setup_models方法进行模型的挂载操作。
def setup_models(self, cr):
""" Complete the setup of models.
This must be called after loading modules and before using the ORM.
"""
lazy_property.reset_all(self)
env = odoo.api.Environment(cr, SUPERUSER_ID, {})
# add manual models
if self._init_modules:
env['ir.model']._add_manual_models()
# prepare the setup on all models
models = list(env.values())
for model in models:
model._prepare_setup()
# do the actual setup from a clean state
self._m2m = {}
for model in models:
model._setup_base()
for model in models:
model._setup_fields()
for model in models:
model._setup_complete()
self.registry_invalidated = True
从代码中可以看出,setup_models方法主要处理模型的挂载任务,首先执行挂载前方法_prepare_setup,然后依次执行了_setup_base,_setup_fields,_setup_complete三个方法完成挂载。
模型挂载完成之后,模块将数据库的结构进行了升级。
最后,完成安装和清理任务。
odoo中有些参数是只能在命令行下输入而不能出现在配置文件中的,这样的参数有: 'publisher_warranty_url', 'load_language', 'root_path', 'init', 'save', 'config', 'update', 'stop_after_init', 'dev_mode', 'shell_interface'
初始化models
模块再挂载完模型之后,将会调用Registry的init_model方法将模型初始化。
def init_models(self, cr, model_names, context):
""" Initialize a list of models (given by their name). Call methods
``_auto_init`` and ``init`` on each model to create or update the
database tables supporting the models.
The ``context`` may contain the following items:
- ``module``: the name of the module being installed/updated, if any;
- ``update_custom_fields``: whether custom fields should be updated.
"""
if 'module' in context:
_logger.info('module %s: creating or updating database tables', context['module'])
elif context.get('models_to_check', False):
_logger.info("verifying fields for every extended model")
env = odoo.api.Environment(cr, SUPERUSER_ID, context)
models = [env[model_name] for model_name in model_names]
for model in models:
model._auto_init()
model.init()
while self._post_init_queue:
func = self._post_init_queue.popleft()
func()
if models:
models[0].recompute()
模型的初始化方法内部实例化了一个环境变量env,然后将模型列表中模型名称一一转换成模型对象,执行其初始化方法。前面我们在讲环境变量的时候提到过,模型的env变量就是此时赋值给模型的实例的。