#!/usr/bin/env python # -*- coding: utf-8 -*- from contextlib import nullcontext import time import fnmatch import logging import optparse from unittest.mock import patch import odoo from . import Command _logger = logging.getLogger(__name__) class Populate(Command): def run(self, cmdargs): parser = odoo.tools.config.parser group = optparse.OptionGroup(parser, "Populate Configuration") group.add_option("--size", dest="population_size", help="Populate database with auto-generated data. Value should be the population size: small, medium or large", default='small') group.add_option("--models", dest='populate_models', help="Comma separated list of model or pattern (fnmatch)") group.add_option("--profile", dest='profiling_enabled', action="store_true", help="Specify if you want to profile records population.", default=False) group.add_option("--rollback", dest='populate_rollback', action="store_true", help="Specify if you want to rollback database population.", default=False) parser.add_option_group(group) opt = odoo.tools.config.parse_config(cmdargs) populate_models = opt.populate_models and set(opt.populate_models.split(',')) dbname = odoo.tools.config['db_name'] registry = odoo.registry(dbname) with registry.cursor() as cr: env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) self.populate( env, opt.population_size, populate_models, profiling_enabled=opt.profiling_enabled, commit=not opt.populate_rollback) @classmethod def populate(cls, env, size, model_patterns=False, profiling_enabled=False, commit=True): registry = env.registry populated_models = None try: registry.populated_models = {} # todo master, initialize with already populated models ordered_models = cls._get_ordered_models(env, model_patterns) _logger.log(25, 'Populating database') for model in ordered_models: if profiling_enabled: profiling_context = odoo.tools.profiler.Profiler( description=f'{model} {size}', db=env.cr.dbname ) else: profiling_context = nullcontext() if commit: commit_context = nullcontext() else: commit_context = patch('odoo.sql_db.Cursor.commit') _logger.info('Populating database for model %s', model._name) t0 = time.time() with profiling_context, commit_context: registry.populated_models[model._name] = model._populate(size).ids if not registry.populated_models[model._name]: # Do not create ir.profile records # for models without any population factories profiling_context.db = False # force the flush to make sure population time still # considers flushing all values to database env.flush_all() if commit: env.cr.commit() model_time = time.time() - t0 if model_time > 1: _logger.info('Populated database for model %s (total: %fs) (average: %fms per record)', model._name, model_time, model_time / len(registry.populated_models[model._name]) * 1000) except: _logger.exception('Something went wrong populating database') finally: if not commit: env.cr.rollback() populated_models = registry.populated_models del registry.populated_models return populated_models @classmethod def _get_ordered_models(cls, env, model_patterns=False): _logger.info('Computing model order') processed = set() ordered_models = [] visited = set() def add_model(model): if model not in processed: if model in visited: raise ValueError('Cyclic dependency detected for %s' % model) visited.add(model) for dep in model._populate_dependencies: add_model(env[dep]) ordered_models.append(model) processed.add(model) for model in env.values(): if model_patterns and not any(fnmatch.fnmatch(model._name, match) for match in model_patterns): continue if model._transient or model._abstract: continue ir_model = env['ir.model'].search([('model', '=', model._name)]) if not model_patterns and all(module.startswith('test_') for module in ir_model.modules.split(',')): continue add_model(model) return ordered_models