Skip to content

Commit bb4bd77

Browse files
committed
[IMP] util/models: check m2m tables on model rename
When renaming a model we need to check m2m tables that may need to be renamed as well. Otherwise the ORM will create a new table that would be empty. If the data is handled directly in the scripts the ignore parameter can be used to avoid warnings. Notes: * From Odoo 9 the column relation_table exists in ir_model_fields * From Odoo 10 the name of m2m tables is given by the model names ordered alphabetically Related: odoo/upgrade#7752
1 parent 30ea7c7 commit bb4bd77

File tree

1 file changed

+101
-2
lines changed

1 file changed

+101
-2
lines changed

src/util/models.py

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,22 @@
1515
from .helpers import _ir_values_value, _validate_model, model_of_table, table_of_model
1616
from .indirect_references import indirect_references
1717
from .inherit import for_each_inherit, inherit_parents
18-
from .misc import _cached, chunks, log_progress
18+
from .misc import _cached, chunks, log_progress, version_gte
1919
from .pg import (
20+
SQLStr,
2021
_get_unique_indexes_with,
2122
column_exists,
2223
column_type,
2324
column_updatable,
2425
explode_execute,
2526
explode_query_range,
2627
format_query,
28+
get_columns,
2729
get_m2m_tables,
2830
get_value_or_en_translation,
2931
parallel_execute,
3032
table_exists,
33+
target_of,
3134
view_exists,
3235
)
3336

@@ -268,7 +271,102 @@ def _replace_model_in_computed_custom_fields(cr, source, target):
268271
)
269272

270273

271-
def rename_model(cr, old, new, rename_table=True):
274+
def _update_m2m_tables(cr, from_model, old_table, new_table, ignored_m2ms=()):
275+
"""
276+
Update m2m table names and columns.
277+
278+
:param str from_model: model for which the underlying table changed
279+
:param str old_table: former table of the model
280+
:param str new_table: new table of the model
281+
:param list(str) ignored_m2ms: explicit list of m2m tables to ignore
282+
283+
:meta private: exclude from online docs
284+
"""
285+
if old_table == new_table or not version_gte("10.0"):
286+
return
287+
m2m_tables = {t for t in get_m2m_tables(cr, new_table) if t not in ignored_m2ms}
288+
cr.execute(
289+
"""
290+
SELECT relation_table,
291+
ARRAY_AGG(id)
292+
FROM ir_model_fields
293+
WHERE ttype = 'many2many'
294+
AND relation_table = ANY(%s)
295+
AND ( model = %s
296+
OR relation = %s)
297+
GROUP BY relation_table
298+
ORDER BY relation_table
299+
""",
300+
[list(m2m_tables), from_model, from_model],
301+
)
302+
for m2m_table, field_ids in cr.fetchall():
303+
m = re.match(r"(\w+)_{0}_rel|{0}_(\w+)_rel".format(re.escape(old_table)), m2m_table)
304+
if not m:
305+
continue
306+
new_m2m_table = "{}_{}_rel".format(*sorted([m.group(1) or m.group(2), new_table]))
307+
# Due to the 63 chars limit in generated constraint names, for long table names the FK
308+
# constraint is dropped when renaming the table. We need the constraint to correctly
309+
# identify the FK targets. The FK constraints will be dropped and recreated below.
310+
pg_rename_table(cr, m2m_table, new_m2m_table, remove_constraints=False)
311+
m2m_tables.remove(m2m_table)
312+
m2m_tables.add(new_m2m_table)
313+
_logger.info("Renamed m2m table %s to %s", m2m_table, new_m2m_table)
314+
cr.execute(
315+
"UPDATE ir_model_fields SET relation_table = %s WHERE id IN %s",
316+
[new_m2m_table, tuple(field_ids)],
317+
)
318+
319+
# Use the full list of tables, some tables are created during the upgrade via create_m2m
320+
# There may be no ir_model_fields entry referencing such tables yet
321+
for m2m_table in sorted(m2m_tables):
322+
for m2m_col in get_columns(cr, m2m_table).iter_unquoted():
323+
col_info = target_of(cr, m2m_table, m2m_col)
324+
if not col_info or col_info[0] != new_table or col_info[1] != "id":
325+
continue
326+
old_col, new_col = map("{}_id".format, [old_table, new_table])
327+
if m2m_col != old_col:
328+
_logger.warning(
329+
"Possibly missing rename: the column %s of m2m table %s references the table %s",
330+
m2m_col,
331+
m2m_table,
332+
new_table,
333+
)
334+
continue
335+
old_constraint = col_info[2]
336+
cr.execute(
337+
"""
338+
SELECT c.confdeltype
339+
FROM pg_constraint c
340+
JOIN pg_class t
341+
ON c.conrelid = t.oid
342+
WHERE t.relname = %s
343+
AND c.conname = %s
344+
""",
345+
[m2m_table, old_constraint],
346+
)
347+
on_delete = cr.fetchone()[0][0]
348+
query = format_query(
349+
cr,
350+
"""
351+
ALTER TABLE {m2m_table}
352+
RENAME COLUMN {old_col} TO {new_col};
353+
354+
ALTER TABLE {m2m_table}
355+
DROP CONSTRAINT {old_constraint},
356+
ADD FOREIGN KEY ({new_col}) REFERENCES {new_table} (id) ON DELETE {del_action}
357+
""",
358+
m2m_table=m2m_table,
359+
old_col=old_col,
360+
new_col=new_col,
361+
old_constraint=old_constraint,
362+
new_table=new_table,
363+
del_action=SQLStr("RESTRICT") if on_delete == "r" else SQLStr("CASCADE"),
364+
)
365+
cr.execute(query)
366+
_logger.info("Renamed m2m column of table %s from %s to %s", m2m_table, old_col, new_col)
367+
368+
369+
def rename_model(cr, old, new, rename_table=True, ignored_m2ms=()):
272370
"""
273371
Rename a model.
274372
@@ -285,6 +383,7 @@ def rename_model(cr, old, new, rename_table=True):
285383
new_table = table_of_model(cr, new)
286384
if new_table != old_table:
287385
pg_rename_table(cr, old_table, new_table)
386+
_update_m2m_tables(cr, old, old_table, new_table, ignored_m2ms)
288387

289388
updates = [("wkf", "osv")] if table_exists(cr, "wkf") else []
290389
updates += [(ir.table, ir.res_model) for ir in indirect_references(cr) if ir.res_model]

0 commit comments

Comments
 (0)