diff --git a/base_exception_compat/README.rst b/base_exception_compat/README.rst new file mode 100644 index 00000000..4069b88c --- /dev/null +++ b/base_exception_compat/README.rst @@ -0,0 +1,88 @@ +.. |company| replace:: ADHOC SA + +.. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png + :alt: ADHOC SA + :target: https://www.adhoc.com.ar + +.. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png + +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +===================== +Base Exception Compat +===================== + +Keeps the exception flow that `base_exception` had before OCA/server-tools#3590, +where `detect_exceptions()` writes in the ongoing transaction and returns the +rules instead of writing through a second cursor and raising. + +The upstream mechanism opens a second database connection to store the +exceptions. That connection writes the same rows the ongoing transaction is +about to write, so the operation either fails with a serialization error or +blocks until the worker is killed. It also turns the exception into an error +for any caller outside the backend web client (portal, e-commerce, API, cron), +because the handler that translates it into a popup is only registered in +`web.assets_backend`. + +The replacement belongs here and not in a module of a specific application: +`detect_exceptions()` is defined on `base.exception.method`, the abstract model +every exception module inherits from, so `sale_exception`, `stock_exception` +and any other one are all affected. Keeping it in `sale_exception_compat` left +stock covered only as long as a sale module happened to be installed, with +nothing declaring that dependency. + +Installation +============ + +To install this module, you need to: + +#. Only need to install the module. It is auto installed with `base_exception`. + +Configuration +============= + +To configure this module, you need to: + +#. Nothing to configure. + +Usage +===== + +To use this module, you need to: + +#. Confirm or validate a record that matches an exception rule: the exception + popup shows up instead of an error. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: http://runbot.adhoc.com.ar/ + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* |company| |icon| + +Contributors +------------ + +Maintainer +---------- + +|company_logo| + +This module is maintained by the |company|. + +To contribute to this module, please visit https://www.adhoc.com.ar. diff --git a/base_exception_compat/__init__.py b/base_exception_compat/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/base_exception_compat/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/base_exception_compat/__manifest__.py b/base_exception_compat/__manifest__.py new file mode 100644 index 00000000..ba40f67a --- /dev/null +++ b/base_exception_compat/__manifest__.py @@ -0,0 +1,12 @@ +{ + "name": "Base Exception Compat", + "version": "18.0.1.1.0", + "category": "Tools", + "author": "ADHOC SA", + "website": "www.adhoc.com.ar", + "license": "AGPL-3", + "summary": "Keeps the pre-3590 exception flow while OCA fixes the second cursor", + "depends": ["base_exception"], + "installable": True, + "auto_install": True, +} diff --git a/base_exception_compat/models/__init__.py b/base_exception_compat/models/__init__.py new file mode 100644 index 00000000..3109b19e --- /dev/null +++ b/base_exception_compat/models/__init__.py @@ -0,0 +1 @@ +from . import base_exception_method diff --git a/base_exception_compat/models/base_exception_method.py b/base_exception_compat/models/base_exception_method.py new file mode 100644 index 00000000..91c620f0 --- /dev/null +++ b/base_exception_compat/models/base_exception_method.py @@ -0,0 +1,24 @@ +from odoo import models +from odoo.fields import Command + + +class BaseExceptionMethod(models.AbstractModel): + _inherit = "base.exception.method" + + def detect_exceptions(self): + # Full replacement of the upstream method: it writes the exceptions + # through a second cursor that commits on the same rows the ongoing + # transaction is about to write, which breaks with a serialization + # failure (or blocks until the worker dies). Write in the current + # transaction and return the rules, as before OCA/server-tools#3590. + if self.env.context.get("test_base_exception"): + # Upstream sets this key to exercise the second cursor on purpose. + # Nothing sets it outside its own tests, so keeping that path alive + # leaves them meaningful instead of red. + return super().detect_exceptions() + all_exception_ids, rules_to_remove, rules_to_add = self._get_exceptions() + for rule_id, records in rules_to_remove.items(): + records.write({"exception_ids": [Command.unlink(rule_id)]}) + for rule_id, records in rules_to_add.items(): + records.write({"exception_ids": [Command.link(rule_id)]}) + return all_exception_ids diff --git a/portal_backend/__manifest__.py b/portal_backend/__manifest__.py index 2c87c48f..616110a9 100644 --- a/portal_backend/__manifest__.py +++ b/portal_backend/__manifest__.py @@ -19,7 +19,7 @@ ############################################################################## { "name": "Portal Backend", - "version": "18.0.1.1.0", + "version": "18.0.1.2.0", "category": "Base", "sequence": 14, "summary": "", diff --git a/portal_backend/controllers/__init__.py b/portal_backend/controllers/__init__.py index 5cfd7701..99cf36ef 100644 --- a/portal_backend/controllers/__init__.py +++ b/portal_backend/controllers/__init__.py @@ -5,3 +5,4 @@ from . import patch_web_controller_utils from . import discuss +from . import thread diff --git a/portal_backend/controllers/thread.py b/portal_backend/controllers/thread.py new file mode 100644 index 00000000..c9053e90 --- /dev/null +++ b/portal_backend/controllers/thread.py @@ -0,0 +1,18 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from odoo import http +from odoo.addons.mail.controllers.thread import ThreadController +from odoo.http import request + + +class PortalBackendThreadController(ThreadController): + @http.route("/mail/thread/data", methods=["POST"], type="json", auth="user") + def mail_thread_data(self, thread_model, thread_id, request_list, **kwargs): + """Core empties request_list for non internal users, so the chatter never gets + followersCount nor attachments and its counters spin forever. Let portal backend users + through: core still checks read access on the record before answering.""" + if request.env.user.has_group("portal_backend.group_portal_backend"): + request.update_context(portal_bypass=True) + return super().mail_thread_data(thread_model, thread_id, request_list, **kwargs) diff --git a/portal_backend/models/mail_thread.py b/portal_backend/models/mail_thread.py index dd46a9da..4c9ab9cc 100644 --- a/portal_backend/models/mail_thread.py +++ b/portal_backend/models/mail_thread.py @@ -6,9 +6,3 @@ class MailThread(models.AbstractModel): message_attachment_count = fields.Integer(groups="base.group_user, portal_backend.group_portal_backend") message_follower_ids = fields.One2many(groups="base.group_user, portal_backend.group_portal_backend") - - def _get_mail_thread_data(self, request_list): - """This avoids to get an error when an internal user is follower of a record that a portal_backend has access""" - if "followers" in request_list and self.env.user.has_group("portal_backend.group_portal_backend"): - request_list.remove("followers") - return super()._get_mail_thread_data(request_list)