diff options
| author | Vo Minh Thu <vmt@openerp.com> | 2011-10-03 17:35:09 +0200 |
|---|---|---|
| committer | Vo Minh Thu <vmt@openerp.com> | 2011-10-03 17:35:09 +0200 |
| commit | d9b6f5e2d0d0c30e4e55bc72454b6ec4b720232c (patch) | |
| tree | 14eb229c961e3413abd06b6ad1a86d64d2d3a2ab /openerp/modules | |
| parent | fbb6e5e56aaf36ff652edbec0181a0fcb95a3601 (diff) | |
| parent | bf3d0d604c89edfd709603c8ca261d020e9ac748 (diff) | |
[MERGE] merged trunk.
bzr revid: vmt@openerp.com-20111003153509-8mbfg1t8hm92pult
Diffstat (limited to 'openerp/modules')
| -rw-r--r-- | openerp/modules/db.py | 22 | ||||
| -rw-r--r-- | openerp/modules/graph.py | 23 | ||||
| -rw-r--r-- | openerp/modules/loading.py | 20 | ||||
| -rw-r--r-- | openerp/modules/module.py | 35 | ||||
| -rw-r--r-- | openerp/modules/registry.py | 142 |
5 files changed, 154 insertions, 88 deletions
diff --git a/openerp/modules/db.py b/openerp/modules/db.py index 25ce7283c97..8650bb5de9a 100644 --- a/openerp/modules/db.py +++ b/openerp/modules/db.py @@ -21,6 +21,7 @@ ############################################################################## import openerp.modules +import logging def is_initialized(cr): """ Check if a database has been initialized for the ORM. @@ -40,6 +41,10 @@ def initialize(cr): """ f = openerp.modules.get_module_resource('base', 'base.sql') + if not f: + m = "File not found: 'base.sql' (provided by module 'base')." + logging.getLogger('init').critical(m) + raise IOError(m) base_sql_file = openerp.tools.misc.file_open(f) try: cr.execute(base_sql_file.read()) @@ -70,13 +75,14 @@ def initialize(cr): cr.execute('INSERT INTO ir_module_module \ (author, website, name, shortdesc, description, \ - category_id, state, certificate, web, license) \ - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id', ( + category_id, state, certificate, web, license, complexity) \ + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id', ( info['author'], info['website'], i, info['name'], info['description'], category_id, state, info['certificate'], info['web'], - info['license'])) + info['license'], + info['complexity'])) id = cr.fetchone()[0] cr.execute('INSERT INTO ir_model_data \ (name,model,module, res_id, noupdate) VALUES (%s,%s,%s,%s,%s)', ( @@ -118,4 +124,14 @@ def create_categories(cr, categories): categories = categories[1:] return p_id +def has_unaccent(cr): + """ Test if the database has an unaccent function. + + The unaccent is supposed to be provided by the PostgreSQL unaccent contrib + module but any similar function will be picked by OpenERP. + + """ + cr.execute("SELECT proname FROM pg_proc WHERE proname='unaccent'") + return len(cr.fetchall()) > 0 + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/modules/graph.py b/openerp/modules/graph.py index 078d23c235b..539dda25b50 100644 --- a/openerp/modules/graph.py +++ b/openerp/modules/graph.py @@ -58,16 +58,16 @@ class Graph(dict): """ - def add_node(self, name, deps): + def add_node(self, name, info): max_depth, father = 0, None - for n in [Node(x, self) for x in deps]: + for n in [Node(x, self, None) for x in info['depends']]: if n.depth >= max_depth: father = n max_depth = n.depth if father: - return father.add_child(name) + return father.add_child(name, info) else: - return Node(name, self) + return Node(name, self, info) def update_from_db(self, cr): if not len(self): @@ -120,7 +120,7 @@ class Graph(dict): continue later.clear() current.remove(package) - node = self.add_node(package, deps) + node = self.add_node(package, info) node.data = info for kind in ('init', 'demo', 'update'): if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force: @@ -154,12 +154,13 @@ class Graph(dict): class Singleton(object): - def __new__(cls, name, graph): + def __new__(cls, name, graph, info): if name in graph: inst = graph[name] else: inst = object.__new__(cls) inst.name = name + inst.info = info graph[name] = inst return inst @@ -167,19 +168,21 @@ class Singleton(object): class Node(Singleton): """ One module in the modules dependency graph. - Node acts as a per-module singleton. + Node acts as a per-module singleton. A node is constructed via + Graph.add_module() or Graph.add_modules(). Some of its fields are from + ir_module_module (setted by Graph.update_from_db()). """ - def __init__(self, name, graph): + def __init__(self, name, graph, info): self.graph = graph if not hasattr(self, 'children'): self.children = [] if not hasattr(self, 'depth'): self.depth = 0 - def add_child(self, name): - node = Node(name, self.graph) + def add_child(self, name, info): + node = Node(name, self.graph, info) node.depth = self.depth + 1 if node not in self.children: self.children.append(node) diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 45a466e5462..b06368ba1b2 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -158,7 +158,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= logger.info('module %s: loading objects', package.name) migrations.migrate_module(package, 'pre') register_module_classes(package.name) - models = pool.instanciate(package.name, cr) + models = pool.load(cr, package) loaded_modules.append(package.name) if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'): init_module_models(cr, package.name, models) @@ -273,8 +273,6 @@ def load_modules(db, force_demo=False, status=None, update_module=False): # This is a brand new pool, just created in pooler.get_db_and_pool() pool = pooler.get_pool(cr.dbname) - processed_modules = [] # for cleanup step after install - loaded_modules = [] # to avoid double loading report = tools.assertion_report() 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')) @@ -285,8 +283,10 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if not graph: logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)') raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)')) - loaded, processed = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report) - processed_modules.extend(processed) + + # processed_modules: for cleanup step after install + # loaded_modules: to avoid double loading + loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report) if tools.config['load_language']: for lang in tools.config['load_language'].split(','): @@ -339,16 +339,16 @@ def load_modules(db, force_demo=False, status=None, update_module=False): cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""") for (model, name) in cr.fetchall(): model_obj = pool.get(model) - if model_obj and not isinstance(model_obj, osv.osv.osv_memory): - logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name)) + if model_obj and not model_obj.is_transient(): + logger.notifyChannel('init', netsvc.LOG_WARNING, 'Model %s (%s) has no access rules!' % (model, name)) # Temporary warning while we remove access rights on osv_memory objects, as they have # been replaced by owner-only access rights cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""") for (model, name) in cr.fetchall(): model_obj = pool.get(model) - if isinstance(model_obj, osv.osv.osv_memory): - logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name)) + if model_obj and model_obj.is_transient(): + logger.notifyChannel('init', netsvc.LOG_WARNING, 'The transient model %s (%s) should not have explicit access rules!' % (model, name)) cr.execute("SELECT model from ir_model") for (model,) in cr.fetchall(): @@ -356,7 +356,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if obj: obj._check_removed_columns(cr, log=True) else: - logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model) + logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)" % model) # Cleanup orphan records pool.get('ir.model.data')._process_end(cr, 1, processed_modules) diff --git a/openerp/modules/module.py b/openerp/modules/module.py index 1514e836dd8..809339897ab 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -31,7 +31,6 @@ import openerp.osv as osv import openerp.tools as tools import openerp.tools.osutil as osutil from openerp.tools.safe_eval import safe_eval as eval -import openerp.pooler as pooler from openerp.tools.translate import _ import openerp.netsvc as netsvc @@ -58,6 +57,11 @@ loaded = [] logger = netsvc.Logger() def initialize_sys_path(): + """ Add all addons paths in sys.path. + + This ensures something like ``import crm`` works even if the addons are + not in the PYTHONPATH. + """ global ad_paths if ad_paths: @@ -245,11 +249,14 @@ def load_information_from_description_file(module): info.setdefault('website', '') info.setdefault('name', False) info.setdefault('description', '') + info.setdefault('complexity', False) info['certificate'] = info.get('certificate') or None info['web'] = info.get('web') or False info['license'] = info.get('license') or 'AGPL-3' info.setdefault('installable', True) info.setdefault('active', False) + # If the following is provided, it is called after the module is --loaded. + info.setdefault('post_load', None) for kind in ['data', 'demo', 'test', 'init_xml', 'update_xml', 'demo_xml']: info.setdefault(kind, []) @@ -271,7 +278,6 @@ def init_module_models(cr, module_name, obj_list): TODO better explanation of _auto_init and init. """ - logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: creating or updating database tables' % module_name) todo = [] @@ -290,23 +296,22 @@ def init_module_models(cr, module_name, obj_list): t[1](cr, *t[2]) cr.commit() +# Import hook to write a addon m in both sys.modules['m'] and +# sys.modules['openerp.addons.m']. Otherwise it could be loaded twice +# if imported twice using different names. +#class MyImportHook(object): +# def find_module(self, module_name, package_path): +# print ">>>", module_name, package_path +# def load_module(self, module_name): +# raise ImportError("Restricted") -def load_module(module_name): - """ Load a Python module found on the addons paths.""" - fm = imp.find_module(module_name, ad_paths) - try: - imp.load_module(module_name, *fm) - finally: - if fm[0]: - fm[0].close() - +#sys.meta_path.append(MyImportHook()) def register_module_classes(m): """ Register module named m, if not already registered. - This will load the module and register all of its models. (Actually, the - explicit constructor call of each of the models inside the module will - register them.) + This loads the module and register all of its models, thanks to either + the MetaModel metaclass, or the explicit instantiation of the model. """ @@ -326,7 +331,7 @@ def register_module_classes(m): try: zip_mod_path = mod_path + '.zip' if not os.path.isfile(zip_mod_path): - load_module(m) + __import__(m) else: zimp = zipimport.zipimporter(zip_mod_path) zimp.load_module(m) diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py index 072082db000..20500f981c3 100644 --- a/openerp/modules/registry.py +++ b/openerp/modules/registry.py @@ -22,10 +22,16 @@ """ Models registries. """ +import threading + +import logging import openerp.sql_db import openerp.osv.orm - +import openerp.cron +import openerp.tools +import openerp.modules.db +import openerp.tools.config class Registry(object): """ Model registry for a particular database. @@ -44,6 +50,14 @@ class Registry(object): self.db_name = db_name self.db = openerp.sql_db.db_connect(db_name) + cr = self.db.cursor() + has_unaccent = openerp.modules.db.has_unaccent(cr) + if openerp.tools.config['unaccent'] and not has_unaccent: + logger = logging.getLogger('unaccent') + logger.warning("The option --unaccent was given but no unaccent() function was found in database.") + self.has_unaccent = openerp.tools.config['unaccent'] and has_unaccent + cr.close() + def do_parent_store(self, cr): for o in self._init_parent: self.get(o)._parent_store_compute(cr) @@ -65,28 +79,42 @@ class Registry(object): """ Return a model for a given name or raise KeyError if it doesn't exist.""" return self.models[model_name] - def instanciate(self, module, cr): - """ Instanciate all the classes of a given module for a particular db.""" + def load(self, cr, module): + """ Load a given module in the registry. + + 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. + + """ res = [] - # Instantiate registered classes (via metamodel discovery or via explicit - # constructor call), and add them to the pool. - for cls in openerp.osv.orm.MetaModel.module_to_models.get(module, []): - res.append(cls.createInstance(self, cr)) + # Instantiate registered classes (via the MetaModel automatic discovery + # or via explicit constructor call), and add them to the pool. + for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []): + res.append(cls.create_instance(self, cr)) return res + def schedule_cron_jobs(self): + """ Make the cron thread care about this registry/database jobs. + This will initiate the cron thread to check for any pending jobs for + this registry/database as soon as possible. Then it will continuously + monitor the ir.cron model for future jobs. See openerp.cron for + details. + """ + openerp.cron.schedule_wakeup(openerp.cron.WAKE_UP_NOW, self.db.dbname) + def clear_caches(self): """ Clear the caches - This clears the caches associated to methods decorated with ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models. """ for model in self.models.itervalues(): model.clear_caches() - class RegistryManager(object): """ Model registries manager. @@ -94,24 +122,20 @@ class RegistryManager(object): registries (essentially database connection/model registry pairs). """ - # Mapping between db name and model registry. # Accessed through the methods below. registries = {} - + registries_lock = threading.RLock() @classmethod def get(cls, db_name, force_demo=False, status=None, update_module=False, pooljobs=True): """ Return a registry for a given database name.""" - - if db_name in cls.registries: - registry = cls.registries[db_name] - else: - registry = cls.new(db_name, force_demo, status, - update_module, pooljobs) - return registry - + try: + return cls.registries[db_name] + except KeyError: + return cls.new(db_name, force_demo, status, + update_module, pooljobs) @classmethod def new(cls, db_name, force_demo=False, status=None, @@ -121,47 +145,64 @@ class RegistryManager(object): The (possibly) previous registry for that database name is discarded. """ - import openerp.modules - registry = Registry(db_name) - - # Initializing a registry will call general code which will in turn - # call registries.get (this object) 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: - # This should be a method on Registry - openerp.modules.load_modules(registry.db, force_demo, status, update_module) - except Exception: - del cls.registries[db_name] - raise - - cr = registry.db.cursor() - try: - registry.do_parent_store(cr) - registry.get('ir.actions.report.xml').register_all(cr) - cr.commit() - finally: - cr.close() + with cls.registries_lock: + registry = Registry(db_name) + + # Initializing a registry will call general code which will in turn + # call registries.get (this object) 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: + # This should be a method on Registry + openerp.modules.load_modules(registry.db, force_demo, status, update_module) + except Exception: + del cls.registries[db_name] + raise + + cr = registry.db.cursor() + try: + registry.do_parent_store(cr) + registry.get('ir.actions.report.xml').register_all(cr) + cr.commit() + finally: + cr.close() if pooljobs: - registry.get('ir.cron').restart(registry.db.dbname) + registry.schedule_cron_jobs() return registry - @classmethod def delete(cls, db_name): - """ Delete the registry linked to a given database. """ - if db_name in cls.registries: - del cls.registries[db_name] + """Delete the registry linked to a given database. + + This also cleans the associated caches. For good measure this also + cancels the associated cron job. But please note that the cron job can + be running and take some time before ending, and that you should not + remove a registry if it can still be used by some thread. So it might + be necessary to call yourself openerp.cron.Agent.cancel(db_name) and + and join (i.e. wait for) the thread. + """ + with cls.registries_lock: + if db_name in cls.registries: + cls.registries[db_name].clear_caches() + del cls.registries[db_name] + openerp.cron.cancel(db_name) @classmethod + def delete_all(cls): + """Delete all the registries. """ + with cls.registries_lock: + for db_name in cls.registries.keys(): + cls.delete(db_name) + + @classmethod def clear_caches(cls, db_name): - """ Clear the caches + """Clear caches This clears the caches associated to methods decorated with ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models @@ -170,8 +211,9 @@ class RegistryManager(object): This method is given to spare you a ``RegistryManager.get(db_name)`` that would loads the given database if it was not already loaded. """ - if db_name in cls.registries: - cls.registries[db_name].clear_caches() + with cls.registries_lock: + if db_name in cls.registries: + cls.registries[db_name].clear_caches() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
