[REF] renamed modules.__init__ to modules.loading.
authorVo Minh Thu <vmt@openerp.com>
Wed, 11 May 2011 17:48:41 +0000 (19:48 +0200)
committerVo Minh Thu <vmt@openerp.com>
Wed, 11 May 2011 17:48:41 +0000 (19:48 +0200)
bzr revid: vmt@openerp.com-20110511174841-wk1yyr9971pu0pwh

openerp/modules/__init__.py
openerp/modules/loading.py [new file with mode: 0644]

index c370166..171d841 100644 (file)
 
 """
 
-import os, sys, imp
-from os.path import join as opj
-import itertools
-import zipimport
-
-import openerp
-
-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
-
-import zipfile
-import openerp.release as release
-
-import re
-import base64
-from zipfile import PyZipFile, ZIP_DEFLATED
-from cStringIO import StringIO
-
-import logging
-
 import openerp.modules.db
 import openerp.modules.graph
+import openerp.modules.loading
 import openerp.modules.migration
+import openerp.modules.module
 
+# TODO temporarily expose those things
 from openerp.modules.module import \
     get_modules, get_modules_with_version, \
     load_information_from_description_file, \
@@ -61,362 +38,7 @@ from openerp.modules.module import \
     get_module_path, initialize_sys_path, \
     register_module_classes, init_module_models
 
-logger = netsvc.Logger()
-
-
-def open_openerp_namespace():
-    # See comment for open_openerp_namespace.
-    if openerp.conf.deprecation.open_openerp_namespace:
-        for k, v in list(sys.modules.items()):
-            if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
-                sys.modules[k[8:]] = v
-
-
-def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
-    """Migrates+Updates or Installs all module nodes from ``graph``
-       :param graph: graph of module nodes to load
-       :param status: status dictionary for keeping track of progress
-       :param perform_checks: whether module descriptors should be checked for validity (prints warnings
-                              for same cases, and even raise osv_except if certificate is invalid)
-       :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
-       :return: list of modules that were installed or updated
-    """
-    def process_sql_file(cr, fp):
-        queries = fp.read().split(';')
-        for query in queries:
-            new_query = ' '.join(query.split())
-            if new_query:
-                cr.execute(new_query)
-
-    def load_init_xml(cr, m, idref, mode):
-        _load_data(cr, m, idref, mode, 'init_xml')
-
-    def load_update_xml(cr, m, idref, mode):
-        _load_data(cr, m, idref, mode, 'update_xml')
-
-    def load_demo_xml(cr, m, idref, mode):
-        _load_data(cr, m, idref, mode, 'demo_xml')
-
-    def load_data(cr, module_name, idref, mode):
-        _load_data(cr, module_name, idref, mode, 'data')
-
-    def load_demo(cr, module_name, idref, mode):
-        _load_data(cr, module_name, idref, mode, 'demo')
-
-    def load_test(cr, module_name, idref, mode):
-        cr.commit()
-        if not tools.config.options['test_disable']:
-            try:
-                _load_data(cr, module_name, idref, mode, 'test')
-            except Exception, e:
-                logging.getLogger('test').exception('Tests failed to execute in module %s', module_name)
-            finally:
-                if tools.config.options['test_commit']:
-                    cr.commit()
-                else:
-                    cr.rollback()
-
-    def _load_data(cr, module_name, idref, mode, kind):
-        """
-
-        kind: data, demo, test, init_xml, update_xml, demo_xml.
-
-        noupdate is False, unless it is demo data or it is csv data in
-        init mode.
-
-        """
-        for filename in package.data[kind]:
-            log = logging.getLogger('init')
-            log.info("module %s: loading %s", module_name, filename)
-            _, ext = os.path.splitext(filename)
-            pathname = os.path.join(module_name, filename)
-            fp = tools.file_open(pathname)
-            noupdate = False
-            if kind in ('demo', 'demo_xml'):
-                noupdate = True
-            try:
-                if ext == '.csv':
-                    if kind in ('init', 'init_xml'):
-                        noupdate = True
-                    tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
-                elif ext == '.sql':
-                    process_sql_file(cr, fp)
-                elif ext == '.yml':
-                    tools.convert_yaml_import(cr, module_name, fp, idref, mode, noupdate)
-                else:
-                    tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
-            finally:
-                fp.close()
-
-    if status is None:
-        status = {}
-
-    processed_modules = []
-    statusi = 0
-    pool = pooler.get_pool(cr.dbname)
-    migrations = openerp.modules.migration.MigrationManager(cr, graph)
-    logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
-
-    # register, instanciate and initialize models for each modules
-    for package in graph:
-        if skip_modules and package.name in skip_modules:
-            continue
-        logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
-        migrations.migrate_module(package, 'pre')
-        register_module_classes(package.name)
-        models = pool.instanciate(package.name, cr)
-        if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
-            init_module_models(cr, package.name, models)
-        cr.commit()
-
-    # load data for each modules
-    modobj = pool.get('ir.module.module')
-    for package in graph:
-        status['progress'] = (float(statusi)+0.1) / len(graph)
-        m = package.name
-        mid = package.id
-
-        if skip_modules and m in skip_modules:
-            continue
-
-        if perform_checks:
-            modobj.check(cr, 1, [mid])
-
-        idref = {}
-        status['progress'] = (float(statusi)+0.4) / len(graph)
-
-        mode = 'update'
-        if hasattr(package, 'init') or package.state == 'to install':
-            mode = 'init'
-
-        if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
-            if package.state=='to upgrade':
-                # upgrading the module information
-                modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
-            load_init_xml(cr, m, idref, mode)
-            load_update_xml(cr, m, idref, mode)
-            load_data(cr, m, idref, mode)
-            if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
-                status['progress'] = (float(statusi)+0.75) / len(graph)
-                load_demo_xml(cr, m, idref, mode)
-                load_demo(cr, m, idref, mode)
-                cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
-
-                # launch tests only in demo mode, as most tests will depend
-                # on demo data. Other tests can be added into the regular
-                # 'data' section, but should probably not alter the data,
-                # as there is no rollback.
-                load_test(cr, m, idref, mode)
-
-            processed_modules.append(package.name)
-
-            migrations.migrate_module(package, 'post')
-
-            ver = release.major_version + '.' + package.data['version']
-            # Set new modules and dependencies
-            modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
-            cr.commit()
-            # Update translations for all installed languages
-            modobj.update_translations(cr, 1, [mid], None)
-            cr.commit()
-
-            package.state = 'installed'
-            for kind in ('init', 'demo', 'update'):
-                if hasattr(package, kind):
-                    delattr(package, kind)
-
-        statusi += 1
-
-    cr.commit()
-
-    return processed_modules
-
-def _check_module_names(cr, module_names):
-    mod_names = set(module_names)
-    if 'base' in mod_names:
-        # ignore dummy 'all' module
-        if 'all' in mod_names:
-            mod_names.remove('all')
-    if mod_names:
-        cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
-        if cr.dictfetchone()['count'] != len(mod_names):
-            # find out what module name(s) are incorrect:
-            cr.execute("SELECT name FROM ir_module_module")
-            incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
-            logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
-
-def load_modules(db, force_demo=False, status=None, update_module=False):
-    # TODO status['progress'] reporting is broken: used twice (and reset each
-    # time to zero) in load_module_graph, not fine-grained enough.
-    # It should be a method exposed by the pool.
-
-    initialize_sys_path()
-
-    open_openerp_namespace()
-
-    cr = db.cursor()
-    if cr:
-        cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
-        if len(cr.fetchall())==0:
-            logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
-            openerp.modules.db.initialize(cr)
-            tools.config["init"]["all"] = 1
-            tools.config['update']['all'] = 1
-            if not tools.config['without_demo']:
-                tools.config["demo"]['all'] = 1
-    force = []
-    if force_demo:
-        force.append('demo')
-
-    # This is a brand new pool, just created in pooler.get_db_and_pool()
-    pool = pooler.get_pool(cr.dbname)
-
-    try:
-        processed_modules = []
-        report = tools.assertion_report()
-        # NOTE: Try to also load the modules that have been marked as uninstallable previously...
-        STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
-        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 = openerp.modules.graph.Graph()
-        graph.add_module(cr, 'base', force)
-        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)'))
-        processed_modules.extend(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(','):
-                tools.load_language(cr, lang)
-
-        # STEP 2: Mark other modules to be loaded/updated
-        if update_module:
-            modobj = pool.get('ir.module.module')
-            if ('base' in tools.config['init']) or ('base' in tools.config['update']):
-                logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
-                modobj.update_list(cr, 1)
-
-            _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
-
-            mods = [k for k in tools.config['init'] if tools.config['init'][k]]
-            if mods:
-                ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
-                if ids:
-                    modobj.button_install(cr, 1, ids)
-
-            mods = [k for k in tools.config['update'] if tools.config['update'][k]]
-            if mods:
-                ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
-                if ids:
-                    modobj.button_upgrade(cr, 1, ids)
-
-            cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
-
-            STATES_TO_LOAD += ['to install']
-
-
-        # STEP 3: Load marked modules (skipping base which was done in STEP 1)
-        loop_guardrail = 0
-        while True:
-            loop_guardrail += 1
-            if loop_guardrail > 100:
-                raise ValueError('Possible recursive module tree detected, aborting.')
-            cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
-
-            module_list = [name for (name,) in cr.fetchall() if name not in graph]
-            if not module_list:
-                break
-
-            new_modules_in_graph = graph.add_modules(cr, module_list, force)
-            if new_modules_in_graph == 0:
-                # nothing to load
-                break
-
-            logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
-            processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
-
-        # load custom models
-        cr.execute('select model from ir_model where state=%s', ('manual',))
-        for model in cr.dictfetchall():
-            pool.get('ir.model').instanciate(cr, 1, model['model'], {})
-
-        # STEP 4: Finish and cleanup
-        if processed_modules:
-            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))
-
-            # 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))
-
-            cr.execute("SELECT model from ir_model")
-            for (model,) in cr.fetchall():
-                obj = pool.get(model)
-                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)
-
-            # Cleanup orphan records
-            pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
-
-        if report.get_report():
-            logger.notifyChannel('init', netsvc.LOG_INFO, report)
-
-        for kind in ('init', 'demo', 'update'):
-            tools.config[kind] = {}
-
-        cr.commit()
-        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 id,name from ir_module_module where state=%s", ('to remove',))
-            for mod_id, mod_name in cr.fetchall():
-                cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
-                for rmod, rid in cr.fetchall():
-                    uid = 1
-                    rmod_module= pool.get(rmod)
-                    if rmod_module:
-                        # TODO group by module so that we can delete multiple ids in a call
-                        rmod_module.unlink(cr, uid, [rid])
-                    else:
-                        logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
-                cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
-                cr.commit()
-
-            # Remove menu items that are not referenced by any of other
-            # (child) menu item, ir_values, or ir_model_data.
-            # This code could be a method of ir_ui_menu.
-            # TODO: remove menu without actions of children
-            while True:
-                cr.execute('''delete from
-                        ir_ui_menu
-                    where
-                        (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
-                    and
-                        (id not IN (select res_id from ir_values where model='ir.ui.menu'))
-                    and
-                        (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
-                cr.commit()
-                if not cr.rowcount:
-                    break
-                else:
-                    logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
-
-            # Pretend that modules to be removed are actually uninstalled.
-            cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
-            cr.commit()
-    finally:
-        cr.close()
+from openerp.modules.loading import load_modules
 
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py
new file mode 100644 (file)
index 0000000..c370166
--- /dev/null
@@ -0,0 +1,422 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+""" Modules (also called addons) management.
+
+"""
+
+import os, sys, imp
+from os.path import join as opj
+import itertools
+import zipimport
+
+import openerp
+
+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
+
+import zipfile
+import openerp.release as release
+
+import re
+import base64
+from zipfile import PyZipFile, ZIP_DEFLATED
+from cStringIO import StringIO
+
+import logging
+
+import openerp.modules.db
+import openerp.modules.graph
+import openerp.modules.migration
+
+from openerp.modules.module import \
+    get_modules, get_modules_with_version, \
+    load_information_from_description_file, \
+    get_module_resource, zip_directory, \
+    get_module_path, initialize_sys_path, \
+    register_module_classes, init_module_models
+
+logger = netsvc.Logger()
+
+
+def open_openerp_namespace():
+    # See comment for open_openerp_namespace.
+    if openerp.conf.deprecation.open_openerp_namespace:
+        for k, v in list(sys.modules.items()):
+            if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
+                sys.modules[k[8:]] = v
+
+
+def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
+    """Migrates+Updates or Installs all module nodes from ``graph``
+       :param graph: graph of module nodes to load
+       :param status: status dictionary for keeping track of progress
+       :param perform_checks: whether module descriptors should be checked for validity (prints warnings
+                              for same cases, and even raise osv_except if certificate is invalid)
+       :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
+       :return: list of modules that were installed or updated
+    """
+    def process_sql_file(cr, fp):
+        queries = fp.read().split(';')
+        for query in queries:
+            new_query = ' '.join(query.split())
+            if new_query:
+                cr.execute(new_query)
+
+    def load_init_xml(cr, m, idref, mode):
+        _load_data(cr, m, idref, mode, 'init_xml')
+
+    def load_update_xml(cr, m, idref, mode):
+        _load_data(cr, m, idref, mode, 'update_xml')
+
+    def load_demo_xml(cr, m, idref, mode):
+        _load_data(cr, m, idref, mode, 'demo_xml')
+
+    def load_data(cr, module_name, idref, mode):
+        _load_data(cr, module_name, idref, mode, 'data')
+
+    def load_demo(cr, module_name, idref, mode):
+        _load_data(cr, module_name, idref, mode, 'demo')
+
+    def load_test(cr, module_name, idref, mode):
+        cr.commit()
+        if not tools.config.options['test_disable']:
+            try:
+                _load_data(cr, module_name, idref, mode, 'test')
+            except Exception, e:
+                logging.getLogger('test').exception('Tests failed to execute in module %s', module_name)
+            finally:
+                if tools.config.options['test_commit']:
+                    cr.commit()
+                else:
+                    cr.rollback()
+
+    def _load_data(cr, module_name, idref, mode, kind):
+        """
+
+        kind: data, demo, test, init_xml, update_xml, demo_xml.
+
+        noupdate is False, unless it is demo data or it is csv data in
+        init mode.
+
+        """
+        for filename in package.data[kind]:
+            log = logging.getLogger('init')
+            log.info("module %s: loading %s", module_name, filename)
+            _, ext = os.path.splitext(filename)
+            pathname = os.path.join(module_name, filename)
+            fp = tools.file_open(pathname)
+            noupdate = False
+            if kind in ('demo', 'demo_xml'):
+                noupdate = True
+            try:
+                if ext == '.csv':
+                    if kind in ('init', 'init_xml'):
+                        noupdate = True
+                    tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
+                elif ext == '.sql':
+                    process_sql_file(cr, fp)
+                elif ext == '.yml':
+                    tools.convert_yaml_import(cr, module_name, fp, idref, mode, noupdate)
+                else:
+                    tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
+            finally:
+                fp.close()
+
+    if status is None:
+        status = {}
+
+    processed_modules = []
+    statusi = 0
+    pool = pooler.get_pool(cr.dbname)
+    migrations = openerp.modules.migration.MigrationManager(cr, graph)
+    logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
+
+    # register, instanciate and initialize models for each modules
+    for package in graph:
+        if skip_modules and package.name in skip_modules:
+            continue
+        logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
+        migrations.migrate_module(package, 'pre')
+        register_module_classes(package.name)
+        models = pool.instanciate(package.name, cr)
+        if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
+            init_module_models(cr, package.name, models)
+        cr.commit()
+
+    # load data for each modules
+    modobj = pool.get('ir.module.module')
+    for package in graph:
+        status['progress'] = (float(statusi)+0.1) / len(graph)
+        m = package.name
+        mid = package.id
+
+        if skip_modules and m in skip_modules:
+            continue
+
+        if perform_checks:
+            modobj.check(cr, 1, [mid])
+
+        idref = {}
+        status['progress'] = (float(statusi)+0.4) / len(graph)
+
+        mode = 'update'
+        if hasattr(package, 'init') or package.state == 'to install':
+            mode = 'init'
+
+        if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
+            if package.state=='to upgrade':
+                # upgrading the module information
+                modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
+            load_init_xml(cr, m, idref, mode)
+            load_update_xml(cr, m, idref, mode)
+            load_data(cr, m, idref, mode)
+            if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
+                status['progress'] = (float(statusi)+0.75) / len(graph)
+                load_demo_xml(cr, m, idref, mode)
+                load_demo(cr, m, idref, mode)
+                cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
+
+                # launch tests only in demo mode, as most tests will depend
+                # on demo data. Other tests can be added into the regular
+                # 'data' section, but should probably not alter the data,
+                # as there is no rollback.
+                load_test(cr, m, idref, mode)
+
+            processed_modules.append(package.name)
+
+            migrations.migrate_module(package, 'post')
+
+            ver = release.major_version + '.' + package.data['version']
+            # Set new modules and dependencies
+            modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
+            cr.commit()
+            # Update translations for all installed languages
+            modobj.update_translations(cr, 1, [mid], None)
+            cr.commit()
+
+            package.state = 'installed'
+            for kind in ('init', 'demo', 'update'):
+                if hasattr(package, kind):
+                    delattr(package, kind)
+
+        statusi += 1
+
+    cr.commit()
+
+    return processed_modules
+
+def _check_module_names(cr, module_names):
+    mod_names = set(module_names)
+    if 'base' in mod_names:
+        # ignore dummy 'all' module
+        if 'all' in mod_names:
+            mod_names.remove('all')
+    if mod_names:
+        cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
+        if cr.dictfetchone()['count'] != len(mod_names):
+            # find out what module name(s) are incorrect:
+            cr.execute("SELECT name FROM ir_module_module")
+            incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
+            logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
+
+def load_modules(db, force_demo=False, status=None, update_module=False):
+    # TODO status['progress'] reporting is broken: used twice (and reset each
+    # time to zero) in load_module_graph, not fine-grained enough.
+    # It should be a method exposed by the pool.
+
+    initialize_sys_path()
+
+    open_openerp_namespace()
+
+    cr = db.cursor()
+    if cr:
+        cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
+        if len(cr.fetchall())==0:
+            logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
+            openerp.modules.db.initialize(cr)
+            tools.config["init"]["all"] = 1
+            tools.config['update']['all'] = 1
+            if not tools.config['without_demo']:
+                tools.config["demo"]['all'] = 1
+    force = []
+    if force_demo:
+        force.append('demo')
+
+    # This is a brand new pool, just created in pooler.get_db_and_pool()
+    pool = pooler.get_pool(cr.dbname)
+
+    try:
+        processed_modules = []
+        report = tools.assertion_report()
+        # NOTE: Try to also load the modules that have been marked as uninstallable previously...
+        STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
+        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 = openerp.modules.graph.Graph()
+        graph.add_module(cr, 'base', force)
+        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)'))
+        processed_modules.extend(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(','):
+                tools.load_language(cr, lang)
+
+        # STEP 2: Mark other modules to be loaded/updated
+        if update_module:
+            modobj = pool.get('ir.module.module')
+            if ('base' in tools.config['init']) or ('base' in tools.config['update']):
+                logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
+                modobj.update_list(cr, 1)
+
+            _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
+
+            mods = [k for k in tools.config['init'] if tools.config['init'][k]]
+            if mods:
+                ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
+                if ids:
+                    modobj.button_install(cr, 1, ids)
+
+            mods = [k for k in tools.config['update'] if tools.config['update'][k]]
+            if mods:
+                ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
+                if ids:
+                    modobj.button_upgrade(cr, 1, ids)
+
+            cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
+
+            STATES_TO_LOAD += ['to install']
+
+
+        # STEP 3: Load marked modules (skipping base which was done in STEP 1)
+        loop_guardrail = 0
+        while True:
+            loop_guardrail += 1
+            if loop_guardrail > 100:
+                raise ValueError('Possible recursive module tree detected, aborting.')
+            cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
+
+            module_list = [name for (name,) in cr.fetchall() if name not in graph]
+            if not module_list:
+                break
+
+            new_modules_in_graph = graph.add_modules(cr, module_list, force)
+            if new_modules_in_graph == 0:
+                # nothing to load
+                break
+
+            logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
+            processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
+
+        # load custom models
+        cr.execute('select model from ir_model where state=%s', ('manual',))
+        for model in cr.dictfetchall():
+            pool.get('ir.model').instanciate(cr, 1, model['model'], {})
+
+        # STEP 4: Finish and cleanup
+        if processed_modules:
+            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))
+
+            # 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))
+
+            cr.execute("SELECT model from ir_model")
+            for (model,) in cr.fetchall():
+                obj = pool.get(model)
+                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)
+
+            # Cleanup orphan records
+            pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
+
+        if report.get_report():
+            logger.notifyChannel('init', netsvc.LOG_INFO, report)
+
+        for kind in ('init', 'demo', 'update'):
+            tools.config[kind] = {}
+
+        cr.commit()
+        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 id,name from ir_module_module where state=%s", ('to remove',))
+            for mod_id, mod_name in cr.fetchall():
+                cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
+                for rmod, rid in cr.fetchall():
+                    uid = 1
+                    rmod_module= pool.get(rmod)
+                    if rmod_module:
+                        # TODO group by module so that we can delete multiple ids in a call
+                        rmod_module.unlink(cr, uid, [rid])
+                    else:
+                        logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
+                cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
+                cr.commit()
+
+            # Remove menu items that are not referenced by any of other
+            # (child) menu item, ir_values, or ir_model_data.
+            # This code could be a method of ir_ui_menu.
+            # TODO: remove menu without actions of children
+            while True:
+                cr.execute('''delete from
+                        ir_ui_menu
+                    where
+                        (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
+                    and
+                        (id not IN (select res_id from ir_values where model='ir.ui.menu'))
+                    and
+                        (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
+                cr.commit()
+                if not cr.rowcount:
+                    break
+                else:
+                    logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
+
+            # Pretend that modules to be removed are actually uninstalled.
+            cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
+            cr.commit()
+    finally:
+        cr.close()
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: