Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMP] util/records: enforce cascade removal for actions #81

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

pauloamed
Copy link
Contributor

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.

@robodoo
Copy link
Contributor

robodoo commented May 6, 2024

@pauloamed
Copy link
Contributor Author

I do not intend to leave these NOTES in the final diff, these are only for discussions in this PR

src/util/records.py Outdated Show resolved Hide resolved
@aj-fuentes
Copy link
Contributor

You could move the notes from the code to the PR. Add them as comments here.

@@ -351,10 +397,13 @@ def remove_record(cr, name):
if model == "res.groups":
_logger.log(NEARLYWARN, "Removing group %r", name)
return remove_group(cr, group_id=res_id)

if model in get_models_inheriting_actions_actions(cr):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have a hard-coded list of standard action models. We don't need to take into account all possible models extending the actions base model. We don't that for views for example.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just use for_each_inherit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my point in leaving it as a SQL query is not only to encompass custom models but also reduce the maintainability overhead in case there is any change in standard models. what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SQL query version plus the query itself adds complexity, it's cleaner with for_each_inherit, IMO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OTOH, the list of models that are problematic (the ones that are using INHERIT at PostgreSQL level) is known. We can hard-code it.

Note that this list changed over time, so we need to list all of them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the ones that are using INHERIT at PostgreSQL level

AFAIU, the issue also encompasses models that would inherit ir.actions.actions without using psql's INHERIT mechanism*, since this concerns fields to ir.actions.actions (as a Polymorphism mechanism), and not its child classes.

*: I've just tested creating a new model inheriting from ir.actions.actions, similar to the standard ones, and this new model's table does not have Inherits: ir_actions, whereas the ones from standard do have.

So I understand that list of models returned by for_each_inherit should be equal to the ones to be hard-coded (or used in a hard-coded list) for a specific version.

That said, having in mind that for_each_inherit performs a SQL query itself, I believe that:

  • hard-coding > for_each_inherit: more performatic
  • for_each_inherit > hard-coding: easier to maintain

@pauloamed pauloamed force-pushed the master-add_actions_cascade_cleanup branch from 2019fa8 to c9c4053 Compare May 6, 2024 15:22
src/util/records.py Outdated Show resolved Hide resolved
src/util/records.py Show resolved Hide resolved
src/util/records.py Show resolved Hide resolved
src/util/records.py Outdated Show resolved Hide resolved
src/util/records.py Outdated Show resolved Hide resolved
"%r should point to a model inheriting from 'ir.actions.actions', not a %r" % (xml_id, model)
)

cr.execute("SELECT name, model FROM ir_model_fields WHERE relation = 'ir.actions.actions' AND on_delete ='cascade'")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also search the one linking to the xmlid's model

Suggested change
cr.execute("SELECT name, model FROM ir_model_fields WHERE relation = 'ir.actions.actions' AND on_delete ='cascade'")
cr.execute("SELECT name, model FROM ir_model_fields WHERE relation IN %s AND on_delete ='cascade'", [tuple({model, "ir.actions.actions"})])

Copy link
Contributor Author

@pauloamed pauloamed May 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understood, maybe this is linked to the comment above?

I took the example of website.snippet.filter that has a M2O field to ir.actions.server. By examining it's table, we have:

Foreign-key constraints:
    "website_snippet_filter_action_server_id_fkey" FOREIGN KEY (action_server_id) REFERENCES ir_act_server(id) ON DELETE CASCADE
    "website_snippet_filter_create_uid_fkey" FOREIGN KEY (create_uid) REFERENCES res_users(id) ON DELETE SET NULL
    "website_snippet_filter_filter_id_fkey" FOREIGN KEY (filter_id) REFERENCES ir_filters(id) ON DELETE CASCADE
    "website_snippet_filter_website_id_fkey" FOREIGN KEY (website_id) REFERENCES website(id) ON DELETE CASCADE
    "website_snippet_filter_write_uid_fkey" FOREIGN KEY (write_uid) REFERENCES res_users(id) ON DELETE SET NULL

That is, the foreign-key to ir.actions.server is there (first line), so we can expect correct behaviour.

The issue is when we have a field to ir.actions.actions for polymorphism.

If indeed a confusion happened, please let me know so I can improve the commit message.

src/util/records.py Outdated Show resolved Hide resolved
src/util/records.py Outdated Show resolved Hide resolved
src/util/records.py Outdated Show resolved Hide resolved
@pauloamed pauloamed force-pushed the master-add_actions_cascade_cleanup branch from c9c4053 to e8ca86d Compare May 7, 2024 08:27
@pauloamed pauloamed force-pushed the master-add_actions_cascade_cleanup branch from 0e6707b to f293017 Compare May 7, 2024 12:49
src/util/records.py Outdated Show resolved Hide resolved
src/util/records.py Outdated Show resolved Hide resolved
@pauloamed pauloamed force-pushed the master-add_actions_cascade_cleanup branch 2 times, most recently from 3d68a7a to 9d3206b Compare May 7, 2024 13:23
src/util/records.py Outdated Show resolved Hide resolved
@pauloamed pauloamed force-pushed the master-add_actions_cascade_cleanup branch from 9d3206b to 8322013 Compare May 7, 2024 14:39
src/util/records.py Outdated Show resolved Hide resolved
@pauloamed pauloamed force-pushed the master-add_actions_cascade_cleanup branch from 8322013 to af0ef5d Compare May 8, 2024 09:36
@KangOl
Copy link
Contributor

KangOl commented May 8, 2024

Can you add a test?

@pauloamed pauloamed force-pushed the master-add_actions_cascade_cleanup branch 2 times, most recently from 2b59b0a to 9fa89bd Compare May 10, 2024 09:08
src/util/records.py Outdated Show resolved Hide resolved
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 when it is being referenced in favour of polymorphism;
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` if `ondelete=cascade`, or setting this field to NULL if
`ondelete=set null`, when the action referenced in such field is removed using
`upgrade-util`.

Co-authored-by: Christophe Simonis <[email protected]>
@pauloamed pauloamed force-pushed the master-add_actions_cascade_cleanup branch from 9fa89bd to 7fc1c85 Compare May 13, 2024 15:25
@pauloamed
Copy link
Contributor Author

pauloamed commented May 17, 2024

Hey I believe we can revert the following PR https://github.com/odoo/upgrade/pull/5869 before this once is merged

@KangOl
Copy link
Contributor

KangOl commented May 23, 2024

Hey I believe we can revert the following PR odoo/upgrade#5869 before this once is merged

No, this is still needed for actions not deleted via this new helper.

@pauloamed
Copy link
Contributor Author

@KangOl @aj-fuentes ping 🏓

@erl-odoo
Copy link
Contributor

Any news here? I've discovered another issue with filters on an upgrade project, though it's not related to upgrading technically -> install a module, like hr_holidays, save a filter, uninstall the module = the filter is now corrupt because of the missing model in registry:
image

IMO, apart from this util helper, either:
1 - have a base script in upgrade that fixes the filters on upgrade, removing ones that have missing actions like the example in this PR and were NOT deleted with this helper, and removes also ones with missing model like I describe above

2 - I fix this in a PR to odoo to cleanup the filters on module / model removal

wdyt? @aj-fuentes @KangOl

@aj-fuentes
Copy link
Contributor

@erl-odoo The uninstall process of standard Odoo is full of corner cases where data is left in a wrong state, this is yet another example :)

IMO yes you can try to fix the standard process to cleanup those filters in master odoo. During upgrades, removing what seems to be broken filters may be acceptable, but we usually try to avoid removing data (they may work in case the model re-appears after a reinstall). Generally speaking, if it was broken before the upgrade and doesn't fail the upgrade, we don't fix it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants