|
43 | 43 |
|
44 | 44 | from .exceptions import MigrationError, SleepyDeveloperError
|
45 | 45 | from .helpers import _validate_table, model_of_table
|
46 |
| -from .misc import Sentinel, log_progress |
| 46 | +from .misc import Sentinel, log_progress, version_gte |
47 | 47 |
|
48 | 48 | _logger = logging.getLogger(__name__)
|
49 | 49 |
|
@@ -1302,6 +1302,80 @@ def create_m2m(cr, m2m, fk1, fk2, col1=None, col2=None):
|
1302 | 1302 | )
|
1303 | 1303 |
|
1304 | 1304 |
|
| 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 | + |
1305 | 1379 | def fixup_m2m(cr, m2m, fk1, fk2, col1=None, col2=None):
|
1306 | 1380 | if col1 is None:
|
1307 | 1381 | col1 = "%s_id" % fk1
|
|
0 commit comments