summaryrefslogtreecommitdiff
path: root/openerp/modules
diff options
context:
space:
mode:
authorVo Minh Thu <vmt@openerp.com>2011-10-03 17:35:09 +0200
committerVo Minh Thu <vmt@openerp.com>2011-10-03 17:35:09 +0200
commitd9b6f5e2d0d0c30e4e55bc72454b6ec4b720232c (patch)
tree14eb229c961e3413abd06b6ad1a86d64d2d3a2ab /openerp/modules
parentfbb6e5e56aaf36ff652edbec0181a0fcb95a3601 (diff)
parentbf3d0d604c89edfd709603c8ca261d020e9ac748 (diff)
[MERGE] merged trunk.
bzr revid: vmt@openerp.com-20111003153509-8mbfg1t8hm92pult
Diffstat (limited to 'openerp/modules')
-rw-r--r--openerp/modules/db.py22
-rw-r--r--openerp/modules/graph.py23
-rw-r--r--openerp/modules/loading.py20
-rw-r--r--openerp/modules/module.py35
-rw-r--r--openerp/modules/registry.py142
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: