Skip to content

Commit 3a9aa15

Browse files
committed
[IMP] util/{models,pg}: 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 86409e5 commit 3a9aa15

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

src/util/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
get_value_or_en_translation,
2929
parallel_execute,
3030
table_exists,
31+
update_m2m_tables,
3132
view_exists,
3233
)
3334

@@ -268,7 +269,7 @@ def _replace_model_in_computed_custom_fields(cr, source, target):
268269
)
269270

270271

271-
def rename_model(cr, old, new, rename_table=True):
272+
def rename_model(cr, old, new, rename_table=True, ignored_m2ms=()):
272273
"""
273274
Rename a model.
274275
@@ -285,6 +286,7 @@ def rename_model(cr, old, new, rename_table=True):
285286
new_table = table_of_model(cr, new)
286287
if new_table != old_table:
287288
pg_rename_table(cr, old_table, new_table)
289+
update_m2m_tables(cr, old_table, new_table, ignored_m2ms)
288290

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

src/util/pg.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
from .exceptions import MigrationError, SleepyDeveloperError
4545
from .helpers import _validate_table, model_of_table
46-
from .misc import Sentinel, log_progress
46+
from .misc import Sentinel, log_progress, version_gte
4747

4848
_logger = logging.getLogger(__name__)
4949

@@ -1300,6 +1300,80 @@ def create_m2m(cr, m2m, fk1, fk2, col1=None, col2=None):
13001300
)
13011301

13021302

1303+
def update_m2m_tables(cr, old_table, new_table, ignored_m2ms=()):
1304+
"""
1305+
Update m2m table names and columns.
1306+
1307+
:param str from_model: model for which the underlying table changed
1308+
:param str old_table: former table of the model
1309+
:param str new_table: new table of the model
1310+
:param list(str) ignored_m2ms: explicit list of m2m tables to ignore
1311+
1312+
:meta private: exclude from online docs
1313+
"""
1314+
if old_table == new_table or not version_gte("10.0"):
1315+
return
1316+
ignored_m2ms = set(ignored_m2ms)
1317+
for orig_m2m_table in get_m2m_tables(cr, new_table):
1318+
if orig_m2m_table in ignored_m2ms:
1319+
continue
1320+
m = re.match(r"^(\w+)_{0}_rel|{0}_(\w+)_rel$".format(re.escape(old_table)), orig_m2m_table)
1321+
if m:
1322+
m2m_table = "{}_{}_rel".format(*sorted([m.group(1) or m.group(2), new_table]))
1323+
# Due to the 63 chars limit in generated constraint names, for long table names the FK
1324+
# constraint is dropped when renaming the table. We need the constraint to correctly
1325+
# identify the FK targets. The FK constraints will be dropped and recreated below.
1326+
rename_table(cr, orig_m2m_table, m2m_table, remove_constraints=False)
1327+
_logger.info("Renamed m2m table %s to %s", orig_m2m_table, m2m_table)
1328+
else:
1329+
m2m_table = orig_m2m_table
1330+
for m2m_col in get_columns(cr, m2m_table).iter_unquoted():
1331+
col_info = target_of(cr, m2m_table, m2m_col)
1332+
if not col_info or col_info[0] != new_table or col_info[1] != "id":
1333+
continue
1334+
old_col, new_col = map("{}_id".format, [old_table, new_table])
1335+
if m2m_col != old_col:
1336+
_logger.warning(
1337+
"Possibly missing rename: the column %s of m2m table %s references the table %s",
1338+
m2m_col,
1339+
m2m_table,
1340+
new_table,
1341+
)
1342+
continue
1343+
old_constraint = col_info[2]
1344+
cr.execute(
1345+
"""
1346+
SELECT c.confdeltype
1347+
FROM pg_constraint c
1348+
JOIN pg_class t
1349+
ON c.conrelid = t.oid
1350+
WHERE t.relname = %s
1351+
AND c.conname = %s
1352+
""",
1353+
[m2m_table, old_constraint],
1354+
)
1355+
on_delete = cr.fetchone()[0][0]
1356+
query = format_query(
1357+
cr,
1358+
"""
1359+
ALTER TABLE {m2m_table}
1360+
RENAME COLUMN {old_col} TO {new_col};
1361+
1362+
ALTER TABLE {m2m_table}
1363+
DROP CONSTRAINT {old_constraint},
1364+
ADD FOREIGN KEY ({new_col}) REFERENCES {new_table} (id) ON DELETE {del_action}
1365+
""",
1366+
m2m_table=m2m_table,
1367+
old_col=old_col,
1368+
new_col=new_col,
1369+
old_constraint=old_constraint,
1370+
new_table=new_table,
1371+
del_action=SQLStr("RESTRICT") if on_delete == "r" else SQLStr("CASCADE"),
1372+
)
1373+
cr.execute(query)
1374+
_logger.info("Renamed m2m column of table %s from %s to %s", m2m_table, old_col, new_col)
1375+
1376+
13031377
def fixup_m2m(cr, m2m, fk1, fk2, col1=None, col2=None):
13041378
if col1 is None:
13051379
col1 = "%s_id" % fk1

0 commit comments

Comments
 (0)