[18.0][FIX] base_exception: Rollback transaction if we detect exceptions#3590
[18.0][FIX] base_exception: Rollback transaction if we detect exceptions#3590grindtildeath wants to merge 10 commits into
Conversation
|
Hi @sebastienbeau, @hparfr, |
| raise_exception = False | ||
| # Write exceptions in a new transaction to be committed so that we can | ||
| # rollback the ongoing one while keeping the exceptions stored | ||
| with Registry(self.env.cr.dbname).cursor() as new_cr: |
There was a problem hiding this comment.
| with Registry(self.env.cr.dbname).cursor() as new_cr: | |
| with self.env.registry.cursor() as new_cr: |
no need to initialize a new registry
|
ping @florian-dacosta |
4ea380d to
61eed8a
Compare
With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call _popup_exception as we used to, but we can use the new hook to have the popup displayed as it used to.
With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call _popup_exception as we used to, but we can use the new hook to have the popup displayed as it used to.
With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call `_popup_exception` as we used to, but we can use the new hook to have the popup displayed smoothly.
With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call _popup_exception as we used to, but we can use the new hook to have the popup displayed smoothly.
…ollback With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call _popup_exception as we used to, but we can use the new hook to have the popup displayed smoothly.
|
ping @jok-adhoc @lav-adhoc @lef-adhoc @maq-adhoc @rousseldenis @florian-dacosta After spending hours on this issue, IMO this solves the core issue we have with non dependent module having their changes done in I then changed the way the exception popup can be displayed and it seems to work smoothly with no side effects and no need of glue modules. Could you guys please have a look, eventually test this (with their dependent PRs) and let me know what do you think? 🙏 |
rousseldenis
left a comment
There was a problem hiding this comment.
LGTM.
Great! @grindtildeath Thanks for this, this seems indeed cleaner to solve the historical issue we have with this.
|
Hello @grindtildeath Thanks for this PR. But I did a test and it is not working as expected. On a demo database with only sale-workflow.webmAs you can see in the video, after clicking on the confirm button of the sale order, I have the new exception and the client order ref field did not change, all good on this part. Can you check this issue please ? |
|
@florian-dacosta Thanks for your feedback. I reworked the JS part to make it cleaner and simpler, it seems to solve your issue and works seamlessly on my side. Would be good if you can test again. |
ivantodorovich
left a comment
There was a problem hiding this comment.
CI fails due to pre-commit
florian-dacosta
left a comment
There was a problem hiding this comment.
@grindtildeath
It seems to work pretty well now, but there is still an issue.
I test with sale_exception, and if I run the cron to test the exceptions on draft order, manually (with button method_direct_trigger), I have this error
AttributeError: The method 'ir.cron.action_popup_exceptions' does not exist
This is because the button on ir.cron do trigger the BaseExceptionError.
|
@florian-dacosta Tanks for the feedback, must be fixed now |
|
Thanks for the fix @grindtildeath It does not seem to be a big problem since the exceptions are commited anyway, but Also, I guess we may have situation with the inverse issue we try to fix here. I am not blocking anything as for now, the gain seems better than the potential loss, but I think it may be an issue. |
simahawk
left a comment
There was a problem hiding this comment.
LGTM. As for version bump I would go for major.
| error.exceptionName === | ||
| "odoo.addons.base_exception.exceptions.BaseExceptionError" | ||
| ) { | ||
| if (error.data.message.split(":")[1].trim() === params.model) { |
There was a problem hiding this comment.
If I get it right when looking at If you register the exception into any of these registries, you won't need this patch:
const errorHandlerRegistry = registry.category("error_handlers");
const errorDialogRegistry = registry.category("error_dialogs");
const errorNotificationRegistry = registry.category("error_notifications");
The second one is probably the right one.
Not a blocker but maybe we can add a TODO here 😉
There was a problem hiding this comment.
Done in fixup commit, but had to use errorHandlerRegistry
|
@florian-dacosta If I get your explanation right, it sounds like a side effect that is not fully in control. To allow devs to still store some information before the exception is raised we could add an hook and pass to it the new env. This way you can control it in a very explicit way. Not a blocker. maybe we can add a TODO in the roadmap. |
I was more thinking on a context to get rid of the raise. Well anyway, not a blocker like I said, maybe it is anticipating an issue we won't ever have. I have no usecase in mind, just thought about the possible side effects. |
a38528c to
f1b8586
Compare
With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call `_popup_exception` as we used to, but we can use the new hook to have the popup displayed smoothly.
|
@grindtildeath may you please rebase with autosquash |
With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call _popup_exception as we used to, but we can use the new hook to have the popup displayed smoothly.
…ollback With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call _popup_exception as we used to, but we can use the new hook to have the popup displayed smoothly.
With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call `_popup_exception` as we used to, but we can use the new hook to have the popup displayed smoothly.
With OCA/server-tools#3590 changing the way exceptions are detected and rollbacked, tests need to be adapted accordingly.
With OCA/server-tools#3590 changing the way exceptions are detected and rollbacked, tests need to be adapted accordingly.
| else self.env | ||
| ) | ||
| for rule_id, records in rules_to_remove.items(): | ||
| records.with_env(new_env).write({"exception_ids": [(3, rule_id)]}) |
There was a problem hiding this comment.
| records.with_env(new_env).write({"exception_ids": [(3, rule_id)]}) | |
| records.with_env(new_env).write({"exception_ids": [Command.unlink(rule_id)]}) |
| for rule_id, records in rules_to_remove.items(): | ||
| records.with_env(new_env).write({"exception_ids": [(3, rule_id)]}) | ||
| for rule_id, records in rules_to_add.items(): | ||
| records.with_env(new_env).write({"exception_ids": [(4, rule_id)]}) |
There was a problem hiding this comment.
| records.with_env(new_env).write({"exception_ids": [(4, rule_id)]}) | |
| records.with_env(new_env).write({"exception_ids": [Command.link(rule_id)]}) |
| import logging | ||
| from collections import defaultdict | ||
|
|
||
| from odoo import _, api, models |
There was a problem hiding this comment.
| from odoo import _, api, models | |
| from odoo import _, api, models, Command |
c3369a1 to
a581627
Compare
This aims to solve issues when modules not depending on whatever implementation of base_exception, override the same function that triggers the detection of exceptions. Before this commit, any changes done in such overrides could end up being committed to the database if the MRO did execute such function before the function implementing base_exception that avoids to call super in case an exception is detected. (eg sale.order. action_confirm in sale_exception) With this commit, in case there is any newly detected exception, or a record with exception that is not ignored, or a blocking exception linked to a record, exception changes will be committed in DB while a specific Exception type will be raised to rollback any changes done in the ongoing transaction. Such an exception will be handled in the UI to refresh the exception_ids field so that the user knows why the action was not completed.
With the previous change raising an exception to rollback a transaction, the function `_popup_exceptions` could not be called anymore while the error was raised. Instead, we provide now a hook `_must_popup_exception` that can be redefined by model and will be called when `action_popup_exception` is called by the webclient, to smoothly refresh the page and display the popup exception wizard.
Simplify the JS layer by leveraging client actions. Return a custom client action whenever the BaseExceptionError is catched on RPC call and handle everything through the call to action_popup_exceptions: - In case exception should not pop up, return a soft reload client action instead of handling the refresh manually in JS - In case exception should pop up, return the wizard action, but do a soft reload first to display the exception_ids block before execution the wizard opening action.
a581627 to
8eedb1e
Compare
| else: | ||
| rec.exceptions_summary = False | ||
|
|
||
| def _must_popup_exception(self): |
There was a problem hiding this comment.
_must_popup_exception -> _should_popup_exception ?
must express a necessity to enforce, not just the intent to show it.
| # rollback the ongoing one while keeping the exceptions stored | ||
| with self.env.registry.cursor() as new_cr: | ||
| new_env = ( | ||
| Environment(new_cr, self.env.uid, self.env.context) |
There was a problem hiding this comment.
We need to call Environment here so that we can patch it in mock_base_exception_method_env
There was a problem hiding this comment.
I think you can use self.env(cr=new_cr) by mocking Environment.__call__(). Though I think it's fine as it is.
| } | ||
|
|
||
| def _must_raise_exception_after_detection(self): | ||
| return not all( |
There was a problem hiding this comment.
Can we decompress the expression like this
main_records = self._get_main_records().filtered("exception_ids")
return not all(
main_records.mapped("ignore_exception")
) or any(rule.is_blocking for rule in records.exception_ids)or
main_records = self._get_main_records().filtered("exception_ids")
ignore_exception = all(
main_records.mapped("ignore_exception")
)
blocking_exception = any(rule.is_blocking for rule in records.exception_ids)
return not ignore_exception or blocking_exception| new_env = ( | ||
| Environment(new_cr, self.env.uid, self.env.context) | ||
| if not test_mode | ||
| else self.env |
There was a problem hiding this comment.
if we use self.env, this means we cannot test to rollback?
There was a problem hiding this comment.
Not sure to understand your concern here 🤨
| # base.exception.method.detect_exceptions, a second savepoint will be | ||
| # created using new_cr, and an odoo.sql_db.Savepoint object will be stored | ||
| # on new_cr._savepoint for this second savepoint. | ||
| # As the with block of assertRaises is exited a rollback to the first |
There was a problem hiding this comment.
Explanation a bit hard to follow IMO. I think that bullet points would help, what do you think?
- Enter
with assertRaises=> Create SavePoint1- Write using
new_cr=> Create SavePoint2 (after SavePoint1)- Exit
with assertRaises=> Rollback to SavePoint1 => invalidate Savepoint2=> We need to discard SavePoint2 to avoid
savepoint does not existerror.
This happens because new_cr and self.env.cr use the same psycopg2 cursor object behind the scene
What's still not clear to me is when the write on new_cr happens
There was a problem hiding this comment.
What's still not clear to me is when the write on new_cr happens
It happens in base.exception.method.detect_exceptions as explained 😬
I added some numbers to explicit each action to avoid rewriting everything, hope it's fine.
| self.assertFalse(self.po.exception_ids) | ||
| self.assertTrue(self.po.with_env(new_env).exception_ids) |
There was a problem hiding this comment.
For the sake of maintainability, would you please write a comment explaining why we expect self.po.exception_ids to have different values according to the chosen Environment?
This aims to solve issues when modules not depending on whatever implementation of base_exception, override the same function that triggers the detection of exceptions.
Before this commit, any changes done in such overrides could end up being committed to the database if the MRO did execute such function before the function implementing base_exception that avoids to call super in case an exception is detected. (eg sale.order. action_confirm in sale_exception)
With this commit, in case there is any newly detected exception, or a record with exception that is not ignored, or a blocking exception linked to a record, exception changes will be committed in DB while a specific Exception type will be raised to rollback any changes done in the ongoing transaction. Such an exception will be handled in the UI to refresh the exception_ids field so that the user knows why the action was not completed.
Replaces and fixes:
Dependent PRs: