Skip to content

Commit 0b51946

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 30ea7c7 commit 0b51946

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

@@ -1302,6 +1302,80 @@ def create_m2m(cr, m2m, fk1, fk2, col1=None, col2=None):
13021302
)
13031303

13041304

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

0 commit comments

Comments
 (0)