Replace the usage of field definitions with field attributes in the ModelStorage

Model.fields_get is expensive to call as it create a new dictionary for each field.

Closes #14061
This commit is contained in:
Cédric Krier
2025-06-10 14:31:11 +02:00
parent 5e560afbdc
commit 1921487213
+39 -46
View File
@@ -539,10 +539,9 @@ class ModelStorage(Model):
for name, value in default.items()
if name.startswith(prefix)}
def convert_data(field_defs, origin, default_values):
def convert_data(fields_, origin, default_values):
data = origin.copy()
for field_name in field_defs:
ftype = field_defs[field_name]['type']
for field_name, field in fields_.items():
field = cls._fields[field_name]
if field_name in (
@@ -564,25 +563,25 @@ class ModelStorage(Model):
elif (isinstance(field, fields.Function)
and not isinstance(field, fields.MultiValue)):
del data[field_name]
elif ftype in ('many2one', 'one2one'):
elif field._type in ('many2one', 'one2one'):
try:
data[field_name] = data[field_name] and \
data[field_name][0]
except Exception:
pass
elif ftype == 'one2many':
elif field._type == 'one2many':
if is_readonly(field) or field.filter:
del data[field_name]
elif data[field_name]:
data[field_name] = [(
'copy', data[field_name],
get_default(field_name))]
elif ftype == 'many2many':
elif field._type == 'many2many':
if is_readonly(field) or field.filter:
del data[field_name]
elif data[field_name]:
data[field_name] = [('add', data[field_name])]
elif ftype == 'binary':
elif field._type == 'binary':
# Copy only file_id
if (field.file_id
and origin.get(field.file_id)
@@ -600,32 +599,31 @@ class ModelStorage(Model):
and field.left and field.right):
mptt.add(field.left)
mptt.add(field.right)
fields_names = [n for n, f in cls._fields.items()
fields_ = {n: f for n, f in cls._fields.items()
if (not isinstance(f, fields.Function)
or isinstance(f, fields.MultiValue))
and n not in mptt]
and n not in mptt}
fields_names = list(fields_.keys())
def _default_fields():
return {k.split('.', 1)[0] for k in default.keys()}
assert _default_fields() <= set(fields_names), (
f"Invalid default fields {_default_fields() - set(fields_names)} "
assert _default_fields() <= fields_.keys(), (
f"Invalid default fields {_default_fields() - fields_.keys()} "
f"for {cls.__name__}")
ids = list(map(int, records))
with without_check_access():
values = {
d['id']: d for d in cls.read(ids, fields_names=fields_names)}
field_defs = cls.fields_get(fields_names=fields_names)
default_values = cls.default_get(fields_names, with_rec_name=False)
to_create = []
for id_ in ids:
data = convert_data(field_defs, values[id_], default_values)
data = convert_data(fields_, values[id_], default_values)
to_create.append(data)
new_records = cls.create(to_create)
fields_translate = {}
for field_name, field in field_defs.items():
if (field_name in cls._fields
and getattr(cls._fields[field_name], 'translate', False)):
for field_name, field in fields_.items():
if getattr(field, 'translate', False):
fields_translate[field_name] = field
if fields_translate:
@@ -1022,10 +1020,9 @@ class ModelStorage(Model):
@error
@lru_cache(maxsize=1000)
def get_many2one(value, relation):
def get_many2one(value, Relation):
if not value:
return None
Relation = pool.get(relation)
res = Relation.search([
('rec_name', '=', value),
], limit=2)
@@ -1045,11 +1042,10 @@ class ModelStorage(Model):
@error
@lru_cache(maxsize=1000)
def get_many2many(value, relation):
def get_many2many(value, Relation):
if not value:
return None
res = []
Relation = pool.get(relation)
for word in next(csv.reader(value.splitlines(), delimiter=',',
quoting=csv.QUOTE_NONE, escapechar='\\')):
res2 = Relation.search([
@@ -1195,7 +1191,8 @@ class ModelStorage(Model):
value=value,
**klass.__names__(field)) from e
def process_lines(data, prefix, fields_def, position=0, klass=cls):
def process_lines(data, prefix, position=0, klass=cls):
fields_ = klass._fields
line = data[position]
row = {}
translate = {}
@@ -1221,27 +1218,25 @@ class ModelStorage(Model):
translate.setdefault(lang, {})[field_name] = line[i]
elif is_prefix_len and prefix == field[:-1]:
field_name = field[-1]
this_field_def = fields_def[field_name]
field_type = this_field_def['type']
field = fields_[field_name]
res = None
if field_name == 'id':
try:
res = int(line[i])
except ValueError:
res = get_many2one(line, i, klass.__name__)
elif field_type == 'many2one':
res = get_many2one(line, i, this_field_def['relation'])
elif field_type in {'many2many', 'one2many'}:
res = get_many2one(line, i, klass)
elif field._type == 'many2one':
res = get_many2one(line, i, field.get_target())
elif field._type in {'many2many', 'one2many'}:
many2many.add(field_name)
res = get_many2many(
line, i, this_field_def['relation'])
res = get_many2many(line, i, field.get_target())
res = [('add', res)] if res else []
elif field_type == 'one2one':
res = get_one2one(line, i, this_field_def['relation'])
elif field_type == 'reference':
elif field._type == 'one2one':
res = get_one2one(line, i, field.get_target())
elif field._type == 'reference':
res = get_reference(line, i, field_name, klass)
else:
res = convert(line, i, field_type, field_name, klass)
res = convert(line, i, field._type, field_name, klass)
row[field_name] = res
elif prefix == field[0:prefix_len]:
todo.add(field[prefix_len])
@@ -1257,11 +1252,11 @@ class ModelStorage(Model):
# Import one2many fields
nbrmax = 1
for field in todo:
Relation = pool.get(fields_def[field]['relation'])
newfd = Relation.fields_get()
for field_name in todo:
field = fields_[field_name]
Relation = field.get_target()
newrow, max2, _ = process_lines(
data, prefix + [field], newfd, position, klass=Relation)
data, prefix + [field_name], position, klass=Relation)
nbrmax = max(nbrmax, max2)
create, write = [], []
dispatch(create, write, newrow, Relation)
@@ -1276,23 +1271,23 @@ class ModelStorage(Model):
if not test:
break
newrow, max2, _ = process_lines(
data, prefix + [field], newfd, position + i,
data, prefix + [field_name], position + i,
klass=Relation)
dispatch(create, write, newrow, Relation)
i += max2
nbrmax = max(nbrmax, i)
row[field] = []
row[field_name] = []
create = [v for v in create if any(v.values())]
if create:
row[field].append(('create', create))
row[field_name].append(('create', create))
if write:
row[field].append(('write',) + tuple(write))
row[field_name].append(('write',) + tuple(write))
if record:
written = set(map(int, sum(write[0:None:2], [])))
delete = [
r.id for r in getattr(record, field)
r.id for r in getattr(record, field_name)
if r.id not in written]
row[field].append(('delete', delete))
row[field_name].append(('delete', delete))
if prefix_len == 0:
for i in range(max(nbrmax, 1)):
@@ -1300,15 +1295,13 @@ class ModelStorage(Model):
return (row, nbrmax, translate)
fields_names = [x.split('/') for x in fields_names]
fields_def = cls.fields_get()
to_create, to_create_translations = [], []
to_write, to_write_translations = [], []
languages = set()
data = data.copy()
while data:
(row, _, translate) = \
process_lines(data, [], fields_def)
(row, _, translate) = process_lines(data, [])
if dispatch(to_create, to_write, row):
to_write_translations.append(translate)
else: