This provides a new API for those operations, in order to make the
distinction between the use cases more explicit. The former API was
using obscure parameter combinations to correspond to various cases.
In the summary below, `fnames` is an iterable of field names. If the
parameter is not given, it means "all fields" in the given context.
Note that method recompute() is now mostly private, as it should not be
used in business code.
# process pending computations and updates
records.env.flush_all() # all fields of all models
records.flush_model(fnames) # the fields of all records of the model
records.flush_recordset(fnames) # the fields of the given records
# process pending computations, became non-public methods
records.env._recompute_all() # all fields of all models
records._recompute_model(fnames) # the fields of all records of the model
records._recompute_recordset(fnames) # the fields of the given records
# invalidate the cache of fields
records.env.invalidate_all() # all fields of all models
records.invalidate_model(fnames) # the fields of all records of the model
records.invalidate_recordset(fnames) # the fields of the given records
Part-of: odoo/odoo#87527
128 lines
5.1 KiB
Python
128 lines
5.1 KiB
Python
#!/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
|