130 lines
5.3 KiB
Python
130 lines
5.3 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import fnmatch
|
|
import logging
|
|
import optparse
|
|
import sys
|
|
import time
|
|
from contextlib import nullcontext
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
import odoo
|
|
|
|
from . import Command
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Populate(Command):
|
|
""" Inject fake data inside a database for testing """
|
|
|
|
def run(self, cmdargs):
|
|
parser = odoo.tools.config.parser
|
|
parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
|
|
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
|