diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py index 426ec9bb653..29f2191f905 100644 --- a/odoo/modules/loading.py +++ b/odoo/modules/loading.py @@ -25,7 +25,8 @@ _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('odoo.tests') -def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None): +def load_module_graph(cr, graph, status=None, perform_checks=True, + skip_modules=None, report=None, models_to_check=None): """Migrates+Updates or Installs all module nodes from ``graph`` :param graph: graph of module nodes to load :param status: deprecated parameter, unused, left to avoid changing signature in 8.0 @@ -96,6 +97,9 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= if kind in ('demo', 'test'): threading.currentThread().testing = False + if models_to_check is None: + models_to_check = set() + processed_modules = [] loaded_modules = [] registry = odoo.registry(cr.dbname) @@ -109,6 +113,8 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= t0 = time.time() t0_sql = odoo.sql_db.sql_counter + models_updated = set() + for index, package in enumerate(graph, 1): module_name = package.name module_id = package.id @@ -130,10 +136,20 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= model_names = registry.load(cr, package) loaded_modules.append(package.name) - if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'): + if (hasattr(package, 'init') or hasattr(package, 'update') + or package.state in ('to install', 'to upgrade')): + models_updated |= set(model_names) + models_to_check -= set(model_names) registry.setup_models(cr) registry.init_models(cr, model_names, {'module': package.name}) cr.commit() + elif package.state != 'to remove': + # The current module has simply been loaded. The models extended by this module + # and for which we updated the schema, must have their schema checked again. + # This is because the extension may have changed the model, + # e.g. adding required=True to an existing field, but the schema has not been + # updated by this module because it's not marked as 'to upgrade/to install'. + models_to_check |= set(model_names) & models_updated idref = {} @@ -228,9 +244,14 @@ def _check_module_names(cr, module_names): incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()]) _logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names)) -def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks): +def load_marked_modules(cr, graph, states, force, progressdict, report, + loaded_modules, perform_checks, models_to_check=None): """Loads modules marked with ``states``, adding them to ``graph`` and ``loaded_modules`` and returns a list of installed/upgraded modules.""" + + if models_to_check is None: + models_to_check = set() + processed_modules = [] while True: cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),)) @@ -239,7 +260,10 @@ def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_m break graph.add_modules(cr, module_list, force) _logger.debug('Updating graph with %d more modules', len(module_list)) - loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks) + loaded, processed = load_module_graph( + cr, graph, progressdict, report=report, skip_modules=loaded_modules, + perform_checks=perform_checks, models_to_check=models_to_check + ) processed_modules.extend(processed) loaded_modules.extend(loaded) if not processed: @@ -253,6 +277,8 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if force_demo: force.append('demo') + models_to_check = set() + cr = db.cursor() try: if not odoo.modules.db.is_initialized(cr): @@ -281,7 +307,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False): # 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) + 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: @@ -336,11 +364,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False): previously_processed = len(processed_modules) processed_modules += load_marked_modules(cr, graph, ['installed', 'to upgrade', 'to remove'], - force, status, report, loaded_modules, update_module) + 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) + loaded_modules, update_module, models_to_check) registry.loaded = True registry.setup_models(cr) @@ -410,6 +438,16 @@ def load_modules(db, force_demo=False, status=None, update_module=False): 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, {}) diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py index 7defc2744f4..1cbe67bd5c3 100644 --- a/odoo/modules/registry.py +++ b/odoo/modules/registry.py @@ -298,6 +298,8 @@ class Registry(Mapping): """ 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]