Skip to content

Commit 1d9e20e

Browse files
pauloamedaj-fuentes
andcommitted
[ADD] util.fields: handle base_import.mapping model/fields renames/removal
Implement proper fixing and/or removal of base_import.mapping and records on model/fields renames/removal: - renamed fields: update affected base_import.mapping records - removed fields: remove affected base_import.mapping records - renamed models: update affected base_import.mapping records `res_model` - removed models: remove affected base_import.mapping records This is a follow-up on 5b944f7 closes odoo#100 Signed-off-by: Christophe Simonis (chs) <[email protected]> Co-authored-by: Alvaro Fuentes <[email protected]>
1 parent 8653cb3 commit 1d9e20e

File tree

5 files changed

+165
-64
lines changed

5 files changed

+165
-64
lines changed

Diff for: src/base/tests/test_util.py

+66
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,72 @@ def test_remove_model(self):
464464
self.assertFalse(self.cr.fetchall())
465465

466466

467+
class TestBaseImportMappings(UnitTestCase):
468+
def setUp(self):
469+
super().setUp()
470+
self.import_mapping = self.env["base_import.mapping"].create(
471+
[
472+
{"res_model": "res.currency", "column_name": "Column name", "field_name": path}
473+
for path in [
474+
"full_name",
475+
"rate_ids/company_id/user_ids/name",
476+
"rate_ids/company_id/user_ids/partner_id/user_ids/name",
477+
"rate_ids/name",
478+
]
479+
]
480+
)
481+
482+
util.flush(self.import_mapping)
483+
484+
def test_rename_field(self):
485+
util.rename_field(self.cr, "res.partner", "user_ids", "renamed_user_ids")
486+
util.invalidate(self.import_mapping)
487+
488+
self.assertEqual(
489+
self.import_mapping[2].field_name, "rate_ids/company_id/user_ids/partner_id/renamed_user_ids/name"
490+
)
491+
492+
util.rename_field(self.cr, "res.users", "name", "new_name")
493+
util.invalidate(self.import_mapping)
494+
495+
self.assertEqual(self.import_mapping[1].field_name, "rate_ids/company_id/user_ids/new_name")
496+
497+
def test_remove_field(self):
498+
prev_mappings = self.env["base_import.mapping"].search([])
499+
500+
util.remove_field(self.cr, "res.currency.rate", "company_id")
501+
util.invalidate(self.import_mapping)
502+
503+
removed_mappings = prev_mappings - self.env["base_import.mapping"].search([])
504+
remaining_mappings = self.import_mapping - removed_mappings
505+
506+
self.assertEqual(len(removed_mappings), 2)
507+
self.assertEqual(remaining_mappings[0].field_name, "full_name")
508+
self.assertEqual(remaining_mappings[1].field_name, "rate_ids/name")
509+
510+
def test_rename_model(self):
511+
util.rename_model(self.cr, "res.currency", "res.currency2")
512+
util.invalidate(self.import_mapping)
513+
514+
self.assertEqual(self.import_mapping[0].res_model, "res.currency2")
515+
516+
def test_remove_model(self):
517+
prev_mappings = self.env["base_import.mapping"].search([])
518+
519+
util.remove_model(self.cr, "res.currency.rate")
520+
util.invalidate(self.import_mapping)
521+
522+
removed_mappings = prev_mappings - self.env["base_import.mapping"].search([])
523+
remaining_mappings = self.import_mapping - removed_mappings
524+
525+
self.assertEqual(len(removed_mappings), 3)
526+
self.assertEqual(remaining_mappings[0].field_name, "full_name")
527+
528+
util.remove_model(self.cr, "res.currency")
529+
self.cr.execute("SELECT * FROM base_import_mapping WHERE id = %s", [remaining_mappings.id])
530+
self.assertFalse(self.cr.fetchall())
531+
532+
467533
class TestIterBrowse(UnitTestCase):
468534
def test_iter_browse_iter(self):
469535
cr = self.env.cr

Diff for: src/util/fields.py

+53-35
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,7 @@ def make_index_name(table_name, column_name):
4040
from .const import ENVIRON
4141
from .domains import _adapt_one_domain, _replace_path, _valid_path_to, adapt_domains
4242
from .exceptions import SleepyDeveloperError
43-
from .helpers import (
44-
_dashboard_actions,
45-
_remove_export_lines,
46-
_validate_model,
47-
resolve_model_fields_path,
48-
table_of_model,
49-
)
43+
from .helpers import _dashboard_actions, _validate_model, resolve_model_fields_path, table_of_model
5044
from .inherit import for_each_inherit
5145
from .misc import SelfPrintEvalContext, log_progress, version_gte
5246
from .orm import env, invalidate
@@ -63,6 +57,7 @@ def make_index_name(table_name, column_name):
6357
savepoint,
6458
table_exists,
6559
)
60+
from .records import _remove_import_export_paths
6661
from .report import add_to_migration_reports, get_anchor_link_to_record
6762

6863
# python3 shims
@@ -208,8 +203,7 @@ def clean_context(context):
208203
[(fieldname, fieldname + " desc"), model, r"\y{}\y".format(fieldname)],
209204
)
210205

211-
# ir.exports.line
212-
_remove_export_lines(cr, model, fieldname)
206+
_remove_import_export_paths(cr, model, fieldname)
213207

214208
def adapter(leaf, is_or, negated):
215209
# replace by TRUE_LEAF, unless negated or in a OR operation but not negated
@@ -969,6 +963,54 @@ def update_field_references(cr, old, new, only_models=None, domain_adapter=None,
969963
return _update_field_usage_multi(cr, models, old, new, domain_adapter=domain_adapter, skip_inherit=skip_inherit)
970964

971965

966+
def _update_impex_renamed_fields_paths(cr, old_field_name, new_field_name, only_models):
967+
export_q = cr.mogrify(
968+
"""
969+
SELECT el.id,
970+
e.resource,
971+
STRING_TO_ARRAY(el.name, '/')
972+
FROM ir_exports_line el
973+
JOIN ir_exports e
974+
ON el.export_id = e.id
975+
WHERE el.name ~ %s
976+
""",
977+
[r"\y{}\y".format(old_field_name)],
978+
).decode()
979+
impex_data = [(export_q, "ir_exports_line", "name")]
980+
if table_exists(cr, "base_import_mapping"):
981+
import_q = cr.mogrify(
982+
"""
983+
SELECT id,
984+
res_model,
985+
STRING_TO_ARRAY(field_name, '/')
986+
FROM base_import_mapping
987+
WHERE field_name ~ %s
988+
""",
989+
[r"\y{}\y".format(old_field_name)],
990+
).decode()
991+
impex_data.append((import_q, "base_import_mapping", "field_name"))
992+
993+
for query, table, column in impex_data:
994+
cr.execute(query)
995+
if not cr.rowcount:
996+
continue
997+
fixed_paths = {}
998+
for record_id, related_model, path in cr.fetchall():
999+
new_path = [
1000+
new_field_name
1001+
if field.field_name == old_field_name and field.field_model in only_models
1002+
else field.field_name
1003+
for field in resolve_model_fields_path(cr, related_model, path)
1004+
]
1005+
if len(new_path) == len(path) and new_path != path:
1006+
fixed_paths[record_id] = "/".join(new_path)
1007+
if fixed_paths:
1008+
cr.execute(
1009+
format_query(cr, "UPDATE {} SET {} = (%s::jsonb)->>(id::text) WHERE id IN %s", table, column),
1010+
[Json(fixed_paths), tuple(fixed_paths)],
1011+
)
1012+
1013+
9721014
def _update_field_usage_multi(cr, models, old, new, domain_adapter=None, skip_inherit=()):
9731015
assert models
9741016
only_models = None if models == "*" else tuple(models)
@@ -1079,33 +1121,9 @@ def _update_field_usage_multi(cr, models, old, new, domain_adapter=None, skip_in
10791121
"""
10801122
cr.execute(q.format(col_prefix=col_prefix), p)
10811123

1082-
# ir.exports.line
1124+
# ir.exports.line, base_import.mapping # noqa
10831125
if only_models:
1084-
cr.execute(
1085-
"""
1086-
SELECT el.id,
1087-
e.resource,
1088-
STRING_TO_ARRAY(el.name, '/')
1089-
FROM ir_exports_line el
1090-
JOIN ir_exports e
1091-
ON el.export_id = e.id
1092-
WHERE el.name ~ %s
1093-
""",
1094-
[r"\y{}\y".format(old)],
1095-
)
1096-
fixed_lines_paths = {}
1097-
for line_id, line_model, line_path in cr.fetchall():
1098-
new_path = [
1099-
new if x.field_name == old and x.field_model in only_models else x.field_name
1100-
for x in resolve_model_fields_path(cr, line_model, line_path)
1101-
]
1102-
if len(new_path) == len(line_path) and new_path != line_path:
1103-
fixed_lines_paths[line_id] = "/".join(new_path)
1104-
if fixed_lines_paths:
1105-
cr.execute(
1106-
"UPDATE ir_exports_line SET name = (%s::jsonb)->>(id::text) WHERE id IN %s",
1107-
[Json(fixed_lines_paths), tuple(fixed_lines_paths)],
1108-
)
1126+
_update_impex_renamed_fields_paths(cr, old, new, only_models)
11091127

11101128
# mail.alias
11111129
if column_exists(cr, "mail_alias", "alias_defaults"):

Diff for: src/util/helpers.py

-24
Original file line numberDiff line numberDiff line change
@@ -317,27 +317,3 @@ def resolve_model_fields_path(cr, model, path):
317317
{"model": model, "path": list(path)},
318318
)
319319
return [FieldsPathPart(**row) for row in cr.dictfetchall()]
320-
321-
322-
def _remove_export_lines(cr, model, field=None):
323-
q = """
324-
SELECT el.id,
325-
e.resource,
326-
STRING_TO_ARRAY(el.name, '/')
327-
FROM ir_exports_line el
328-
JOIN ir_exports e
329-
ON el.export_id = e.id
330-
"""
331-
if field:
332-
q = cr.mogrify(q + " WHERE el.name ~ %s ", [r"\y{}\y".format(field)]).decode()
333-
cr.execute(q)
334-
to_rem = [
335-
line_id
336-
for line_id, line_model, line_path in cr.fetchall()
337-
if any(
338-
x.field_model == model and (field is None or x.field_name == field)
339-
for x in resolve_model_fields_path(cr, line_model, line_path)
340-
)
341-
]
342-
if to_rem:
343-
cr.execute("DELETE FROM ir_exports_line WHERE id IN %s", [tuple(to_rem)])

Diff for: src/util/models.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from .const import ENVIRON
1313
from .fields import IMD_FIELD_PATTERN, remove_field
14-
from .helpers import _ir_values_value, _remove_export_lines, _validate_model, model_of_table, table_of_model
14+
from .helpers import _ir_values_value, _validate_model, model_of_table, table_of_model
1515
from .indirect_references import indirect_references
1616
from .inherit import for_each_inherit, inherit_parents
1717
from .misc import _cached, chunks, log_progress
@@ -31,7 +31,7 @@
3131

3232
# avoid namespace clash
3333
from .pg import rename_table as pg_rename_table
34-
from .records import _rm_refs, remove_records, remove_view, replace_record_references_batch
34+
from .records import _remove_import_export_paths, _rm_refs, remove_records, remove_view, replace_record_references_batch
3535
from .report import add_to_migration_reports
3636

3737
_logger = logging.getLogger(__name__)
@@ -129,8 +129,7 @@ def remove_model(cr, model, drop_table=True, ignore_m2m=()):
129129
cr.execute(query, args + (tuple(ids),))
130130
notify = notify or bool(cr.rowcount)
131131

132-
# for ir.exports.line we have to take care of "nested" references in fields "paths"
133-
_remove_export_lines(cr, model)
132+
_remove_import_export_paths(cr, model)
134133

135134
_rm_refs(cr, model)
136135

Diff for: src/util/records.py

+43-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@
2323

2424
from .const import NEARLYWARN
2525
from .exceptions import MigrationError
26-
from .helpers import _get_theme_models, _ir_values_value, _validate_model, model_of_table, table_of_model
26+
from .helpers import (
27+
_get_theme_models,
28+
_ir_values_value,
29+
_validate_model,
30+
model_of_table,
31+
resolve_model_fields_path,
32+
table_of_model,
33+
)
2734
from .indirect_references import indirect_references
2835
from .inherit import direct_inherit_parents, for_each_inherit
2936
from .misc import parse_version, version_gte
@@ -474,6 +481,41 @@ def _rm_refs(cr, model, ids=None):
474481
)
475482

476483

484+
def _remove_import_export_paths(cr, model, field=None):
485+
export_q = """
486+
SELECT el.id,
487+
e.resource,
488+
STRING_TO_ARRAY(el.name, '/')
489+
FROM ir_exports_line el
490+
JOIN ir_exports e
491+
ON el.export_id = e.id
492+
"""
493+
if field:
494+
export_q = cr.mogrify(export_q + " WHERE el.name ~ %s ", [r"\y{}\y".format(field)]).decode()
495+
496+
import_q = """
497+
SELECT id,
498+
res_model,
499+
STRING_TO_ARRAY(field_name, '/')
500+
FROM base_import_mapping
501+
"""
502+
if field:
503+
import_q = cr.mogrify(import_q + " WHERE field_name ~ %s ", [r"\y{}\y".format(field)]).decode()
504+
505+
for query, impex_model in [(export_q, "ir.exports.line"), (import_q, "base_import.mapping")]:
506+
cr.execute(query)
507+
to_rem = [
508+
path_id
509+
for path_id, related_model, path in cr.fetchall()
510+
if any(
511+
x.field_model == model and (field is None or x.field_name == field)
512+
for x in resolve_model_fields_path(cr, related_model, path)
513+
)
514+
]
515+
if to_rem:
516+
remove_records(cr, impex_model, to_rem)
517+
518+
477519
def is_changed(cr, xmlid, interval="1 minute"):
478520
"""
479521
Return whether a record was changed.

0 commit comments

Comments
 (0)