15
15
from .helpers import _ir_values_value , _validate_model , model_of_table , table_of_model
16
16
from .indirect_references import indirect_references
17
17
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
19
19
from .pg import (
20
+ SQLStr ,
20
21
_get_unique_indexes_with ,
21
22
column_exists ,
22
23
column_type ,
23
24
column_updatable ,
24
25
explode_execute ,
25
26
explode_query_range ,
26
27
format_query ,
28
+ get_columns ,
27
29
get_m2m_tables ,
28
30
get_value_or_en_translation ,
29
31
parallel_execute ,
30
32
table_exists ,
33
+ target_of ,
31
34
view_exists ,
32
35
)
33
36
@@ -268,7 +271,102 @@ def _replace_model_in_computed_custom_fields(cr, source, target):
268
271
)
269
272
270
273
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 = ()):
272
370
"""
273
371
Rename a model.
274
372
@@ -285,6 +383,7 @@ def rename_model(cr, old, new, rename_table=True):
285
383
new_table = table_of_model (cr , new )
286
384
if new_table != old_table :
287
385
pg_rename_table (cr , old_table , new_table )
386
+ _update_m2m_tables (cr , old , old_table , new_table , ignored_m2ms )
288
387
289
388
updates = [("wkf" , "osv" )] if table_exists (cr , "wkf" ) else []
290
389
updates += [(ir .table , ir .res_model ) for ir in indirect_references (cr ) if ir .res_model ]
0 commit comments