Skip to content

[17.0][FIX] base: cascade inactive child views on force unlink#9

Open
eantones wants to merge 1 commit into
17.0from
17.0-fix-view-unlink-inactive-children
Open

[17.0][FIX] base: cascade inactive child views on force unlink#9
eantones wants to merge 1 commit into
17.0from
17.0-fix-view-unlink-inactive-children

Conversation

@eantones

@eantones eantones commented Jul 15, 2026

Copy link
Copy Markdown
Member

Obsolete parent view is left orphaned when its inherited child view is inactive

Impacted versions:

  • 17.0, 18.0 (present unchanged since 10.0)

Description of the issue/feature this PR addresses:

View.unlink() emulates an ondelete cascade over inherit_children_ids when _force_unlink is set:

def unlink(self):
    # if in uninstall mode and has children views, emulate an ondelete cascade
    if self.env.context.get('_force_unlink', False) and self.inherit_children_ids:
        self.inherit_children_ids.unlink()

inherit_children_ids is a plain One2many, so it honours active_test and inactive child views are never returned. The cascade skips them, but they still hold the inherit_id RESTRICT foreign key, so the parent's DELETE is rejected. It is logged as a benign bad query, execution continues, and the obsolete parent view is left orphaned.

This is not specific to upgrades. _force_unlink is set in exactly two places, both ordinary operations:

  • every module update or installir.model.data._process_end() sets it while removing records that became obsolete, and is called from odoo/modules/loading.py ("Cleanup orphan records") on every -u / -i;
  • every module uninstallir.model.data._module_data_uninstall() sets it.

A later -u does not clean it up either: _process_end() walks its obsolete records once and never revisits the parent, and every subsequent run re-executes the same cascade, skips the same inactive child, and re-fails the same DELETE.

Two conditions must coincide, which is why this is rarely noticed: the parent must be processed before the child (_process_end orders by ir_model_data.id DESC), and the child must be inactive. Either one alone is harmless — an active child is cascaded away, and a child processed first drops the foreign key before the parent's turn.

Steps to reproduce:

  1. Upgrade a database from 16.0 to 17.0. website_sale.payment_footer (parent) and website_sale.payment_sale_note (child, inherit_id="payment_footer", active="False") are both dropped from core in 17.0.
  2. Watch the log during the update.
  3. Query ir_ui_view for website_sale.payment_footer after the update.

Current behavior before PR:

Deleting 6740@ir.ui.view (website_sale.payment_footer)      <- parent first
bad query: DELETE FROM "ir_ui_view" WHERE id IN (6740)      <- rejected by ir_ui_view_inherit_id_fkey
Deleting 5500@ir.ui.view (website_sale.payment_sale_note)   <- child, later

The parent view stays in ir_ui_view permanently: the cascade did not see the inactive child, the foreign key rejected the DELETE, and nothing retries it.

Desired behavior after PR is merged:

The cascade reads the children with active_test=False, so the inactive child is unlinked before the parent's DELETE, which then succeeds. No orphan is left behind, and uninstalling a module that owns such a pair removes both views.

View.unlink() emulates an ondelete cascade on inherit_children_ids when
_force_unlink is set, but reads that One2many with active_test enabled, so
INACTIVE child views are skipped. They still hold the inherit_id RESTRICT
foreign key, so the parent's DELETE is rejected, logged as a benign
bad query, and the obsolete parent view survives orphaned forever --
_process_end never revisits it and a later -u all does not sweep it.

Reproduced upgrading 16.0 -> 17.0: website_sale.payment_footer is dropped
from core in 17.0 but survives, because its child payment_sale_note is
active="False" and is not cascaded.

Read inherit_children_ids with active_test=False so the cascade covers
inactive children too.
@eantones eantones added the odoo-candidate Contains commits of ours that will be proposed to odoo/odoo at submit time. label Jul 15, 2026
@eantones eantones changed the title [FIX] base: cascade inactive child views on force unlink [17.0][FIX] base: cascade inactive child views on force unlink Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

odoo-candidate Contains commits of ours that will be proposed to odoo/odoo at submit time.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant