From 9cb75ad822e5547f532e2db5badcd9c8ff38f7ad Mon Sep 17 00:00:00 2001 From: Omar Assouma Date: Wed, 26 Aug 2026 16:01:51 +0400 Subject: [PATCH] [FIX] endpoint_route_handler: build routing map without a bound request routing_map() is wrapped with an ormcache whose key calls _endpoint_route_last_version(), and the routing rules are injected by _endpoint_routing_rules(). Both are classmethods, so neither has self.env and both reach the endpoint registry through http.request.env. Off a request that proxy is unbound and raises "RuntimeError: object is not bound". Odoo 19 calls routing_map() that way: website.technical.page builds its SQL view from get_static_routes(), which walks the map, and website's TestWebsiteTechnicalPage exercises it from a TransactionCase. Crons and `odoo shell` hit the same wall. The existing tests never caught it because they all wrap their calls in MockRequest. routing_map() is a model method, so self.env already holds a cursor and the request was never needed. Make both helpers instance methods reading self.env. They are only called as self.() from this module, and the tests already call _endpoint_route_last_version() on a recordset, so no call site changes. last_version() reads a sequence, so any cursor on the database returns the same value and the cache key is unchanged. Add a regression test that builds the map with no request bound. --- endpoint_route_handler/models/ir_http.py | 10 ++++------ endpoint_route_handler/readme/CONTRIBUTORS.md | 1 + endpoint_route_handler/tests/test_endpoint.py | 8 ++++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/endpoint_route_handler/models/ir_http.py b/endpoint_route_handler/models/ir_http.py index e7b83c72..de30ae12 100644 --- a/endpoint_route_handler/models/ir_http.py +++ b/endpoint_route_handler/models/ir_http.py @@ -28,10 +28,9 @@ def _generate_routing_rules(self, modules, converters): self._endpoint_routing_rules(), ) - @classmethod - def _endpoint_routing_rules(cls): + def _endpoint_routing_rules(self): """Yield custom endpoint rules""" - e_registry = cls._endpoint_route_registry(http.request.env) + e_registry = self._endpoint_route_registry(self.env) for endpoint_rule in e_registry.get_rules(): _logger.debug("LOADING %s", endpoint_rule) endpoint = endpoint_rule.endpoint @@ -43,9 +42,8 @@ def routing_map(self, key=None): res = super().routing_map(key=key) return res - @classmethod - def _endpoint_route_last_version(cls): - res = cls._get_routing_map_last_version(http.request.env) + def _endpoint_route_last_version(self): + res = self._get_routing_map_last_version(self.env) return res @classmethod diff --git a/endpoint_route_handler/readme/CONTRIBUTORS.md b/endpoint_route_handler/readme/CONTRIBUTORS.md index 970400e8..db4174db 100644 --- a/endpoint_route_handler/readme/CONTRIBUTORS.md +++ b/endpoint_route_handler/readme/CONTRIBUTORS.md @@ -1,2 +1,3 @@ - Simone Orsi \<\> - Nguyen Minh Chien \<\> +- Omar Assouma \<\> diff --git a/endpoint_route_handler/tests/test_endpoint.py b/endpoint_route_handler/tests/test_endpoint.py index 14901918..cebcae17 100644 --- a/endpoint_route_handler/tests/test_endpoint.py +++ b/endpoint_route_handler/tests/test_endpoint.py @@ -42,6 +42,14 @@ def tearDown(self): EndpointRegistry.wipe_registry_for(self.env.cr) super().tearDown() + def test_routing_map_no_request(self): + # Crons, `odoo shell` and core tests build the routing map with no + # request bound. website's TestWebsiteTechnicalPage does, through + # website.technical.page.get_static_routes(). No mocked request here + # on purpose. + self.env.registry.clear_cache("routing") + self.assertTrue(self.env["ir.http"].routing_map()) + def test_as_tool_base_data(self): new_route = make_new_route(self.env) self.assertEqual(new_route.route, "/my/test/route")