Skip to content

Commit f293017

Browse files
pauloamedKangOl
andcommitted
[IMP] util/records: enforce cascade removal for actions
The implementation of the python inheritance mechanism between the base class `ir.actions.actions` and its child classes (eg. `ir.actions.act_window`) does not allow the creation of foreign keys when `ir.actions.actions` is a M2O field of another model; what leads to the not execution of some constraints, one of them being the `ondelete='cascade'` constraint, which is set in PSQL level. That said, when a `ir.actions.actions` record is deleted, if it is being referenced as a M2O field by another model (eg. `ir.filters`), records from this second model won't be affected, what leads to undesired behaviour: a MissingError in the UI, indicating that the action was deleted. Such behaviour of not creating foreign keys and thus constraints is specific to `ir.actions.actions`. This commit remedies this specific case, removing records with a M2O field to `ir.actions.actions` with `ondelete=cascade` when the action referenced in such field is removed using `upgrade-util`. Co-authored-by: Christophe Simonis <[email protected]>
1 parent 3787631 commit f293017

File tree

1 file changed

+81
-1
lines changed

1 file changed

+81
-1
lines changed

src/util/records.py

+81-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
_get_unique_indexes_with,
3333
_validate_table,
3434
column_exists,
35+
column_nullable,
3536
column_type,
3637
column_updatable,
3738
explode_execute,
@@ -309,6 +310,82 @@ def remove_asset(cr, name):
309310
remove_view(cr, name, silent=True)
310311
# fmt:on
311312

313+
def get_models_inheriting_actions_actions(cr):
314+
cr.execute(
315+
"""
316+
SELECT child_model.model
317+
FROM ir_model AS child_model
318+
JOIN ir_model_inherit
319+
ON child_model.id = ir_model_inherit.model_id
320+
JOIN ir_model AS base_model
321+
ON ir_model_inherit.parent_id = base_model.id
322+
WHERE base_model.model = 'ir.actions.actions'
323+
"""
324+
)
325+
return cr.fetchall()
326+
327+
def remove_action(cr, xml_id=None, action_id=None):
328+
assert bool(xml_id) ^ bool(action_id)
329+
330+
if xml_id:
331+
action_id = ref(cr, xml_id)
332+
333+
action_model = None
334+
if action_id:
335+
module, _, name = xml_id.partition(".")
336+
cr.execute("SELECT model FROM ir_model_data WHERE module=%s AND name=%s", [module, name])
337+
[action_model] = cr.fetchone()
338+
if not action_model in get_models_inheriting_actions_actions(cr):
339+
raise ValueError(
340+
"%r should point to a model inheriting from 'ir.actions.actions', not a %r" % (xml_id, action_model)
341+
)
342+
343+
if version_gte("11.0"):
344+
cr.execute(
345+
"""
346+
SELECT name,
347+
model,
348+
on_delete
349+
FROM ir_model_fields
350+
WHERE relation = 'ir.actions.actions'
351+
AND on_delete IN ( 'cascade', 'set null' )
352+
AND ttype = 'many2one'
353+
"""
354+
)
355+
356+
for column_name, model, on_delete in cr.fetchall():
357+
model_table = table_of_model(cr, model)
358+
if on_delete == 'cascade':
359+
query = format_query(cr, "DELETE FROM {} WHERE {} = %s", model_table, column_name)
360+
else:
361+
query = format_query(cr, "UPDATE {} SET {} = NULL WHERE {} = %s", model_table, column_name, column_name)
362+
cr.execute(query, (action_id,))
363+
else:
364+
# Before V11, we did not have `on_delete` on `ir_model_fields`.
365+
# However, we can infer the intended behaviour by checking if the column containing the deleted action's id is
366+
# nullable or not:
367+
# if not nullable, the record containing the deleted action should also be deleted (cascade);
368+
# else, we just set the column to NULL (set null)
369+
cr.execute(
370+
"""
371+
SELECT name,
372+
model
373+
FROM ir_model_fields
374+
WHERE relation = 'ir.actions.actions'
375+
AND ttype = 'many2one'
376+
"""
377+
)
378+
379+
for column_name, model in cr.fetchall():
380+
model_table = table_of_model(cr, model)
381+
if not column_nullable(cr, model_of_table, column_name):
382+
query = format_query(cr, "DELETE FROM {} WHERE {} = %s", model_table, column_name)
383+
else:
384+
query = format_query(cr, "UPDATE {} SET {} = NULL WHERE {} = %s", model_table, column_name, column_name)
385+
cr.execute(query, (action_id,))
386+
387+
remove_records(cr, action_model, [action_id])
388+
312389

313390
def remove_record(cr, name):
314391
"""
@@ -351,10 +428,13 @@ def remove_record(cr, name):
351428
if model == "res.groups":
352429
_logger.log(NEARLYWARN, "Removing group %r", name)
353430
return remove_group(cr, group_id=res_id)
431+
432+
if model in get_models_inheriting_actions_actions(cr):
433+
_logger.log(NEARLYWARN, "Removing action %r", name)
434+
return remove_action(cr, action_id=res_id)
354435

355436
return remove_records(cr, model, [res_id])
356437

357-
358438
def remove_records(cr, model, ids):
359439
if not ids:
360440
return

0 commit comments

Comments
 (0)