diff --git a/endpoint/models/endpoint_mixin.py b/endpoint/models/endpoint_mixin.py index d998d205..f0897f5f 100644 --- a/endpoint/models/endpoint_mixin.py +++ b/endpoint/models/endpoint_mixin.py @@ -55,6 +55,13 @@ class EndpointMixin(models.AbstractModel): ) exec_as_user_id = fields.Many2one(comodel_name="res.users") company_id = fields.Many2one("res.company", string="Company") + full_url_display = fields.Char( + compute="_compute_full_url_display", + help="Absolute URL of this endpoint, ready to copy/paste. Resolved " + "from the current request when available (matching what you're " + "actually browsing), otherwise falls back to the 'web.base.url' " + "system parameter.", + ) def _selection_exec_mode(self): return [("code", "Execute code")] @@ -63,6 +70,20 @@ def _compute_code_snippet_docs(self): for rec in self: rec.code_snippet_docs = textwrap.dedent(rec._default_code_snippet_docs()) + @api.depends("route") + def _compute_full_url_display(self): + base_url = self._get_current_base_url() + for rec in self: + rec.full_url_display = f"{base_url}{rec.route}" if rec.route else False + + def _get_current_base_url(self): + # Prefer the host the caller is actually browsing on: `web.base.url` + # is a manually maintained system parameter and easily out of sync + # (wrong scheme, stale domain after a move, ...). + if http.request: + return http.request.httprequest.url_root.rstrip("/") + return self.env["ir.config_parameter"].sudo().get_param("web.base.url") + @api.constrains("exec_mode") def _check_exec_mode(self): for rec in self: diff --git a/endpoint/tests/test_endpoint.py b/endpoint/tests/test_endpoint.py index 9827b31a..9a542173 100644 --- a/endpoint/tests/test_endpoint.py +++ b/endpoint/tests/test_endpoint.py @@ -252,6 +252,29 @@ def test_duplicate(self): endpoint = self.endpoint.copy() self.assertTrue(endpoint.route.endswith("/COPY_FIXME")) + def test_full_url_display_from_request(self): + # Prefer the current request's own host over `web.base.url`. + self.endpoint.invalidate_recordset(["full_url_display"]) + with self._get_mocked_request( + httprequest={"url_root": "https://mocked.example.com/"} + ): + self.assertEqual( + self.endpoint.full_url_display, + f"https://mocked.example.com{self.endpoint.route}", + ) + + def test_full_url_display_fallback_to_web_base_url(self): + # No request in context (e.g. a cron or a shell session): fall back + # to the `web.base.url` system parameter. + self.env["ir.config_parameter"].sudo().set_param( + "web.base.url", "https://fallback.example.com" + ) + self.endpoint.invalidate_recordset(["full_url_display"]) + self.assertEqual( + self.endpoint.full_url_display, + f"https://fallback.example.com{self.endpoint.route}", + ) + def _json_response(self, result): """Render a result through the controller, as a request would.""" with self._get_mocked_request() as req: diff --git a/endpoint/views/endpoint_view.xml b/endpoint/views/endpoint_view.xml index a267153d..487d6e48 100644 --- a/endpoint/views/endpoint_view.xml +++ b/endpoint/views/endpoint_view.xml @@ -18,6 +18,14 @@ invisible="active" /> +