diff --git a/server_environment/models/server_env_mixin.py b/server_environment/models/server_env_mixin.py index fa83ae642..f2f4997b8 100644 --- a/server_environment/models/server_env_mixin.py +++ b/server_environment/models/server_env_mixin.py @@ -277,6 +277,14 @@ def _compute_server_env(self): else: record._compute_server_env_from_default(field_name, options) + def _search_server_env(self, field_name, operator, value): + # env-computed fields are not stored, so we can't search them in SQL. + # Load all records and filter them in memory instead. These config + # models hold very few records, so the full scan is acceptable. + all_records = self.search([]) # pylint: disable=no-search-all + matched = all_records.filtered_domain([(field_name, operator, value)]) + return [("id", "in", matched.ids)] + def _inverse_server_env(self, field_name): options = self._server_env_fields[field_name] default_field = self._server_env_default_fieldname(field_name) @@ -358,6 +366,16 @@ def _server_env_transform_field_to_read_from_env(self, field): ) setattr(type(self), inverse_method_name, inverse_method) field.inverse = inverse_method_name + + search_method_name = f"_search_server_env_{field.name}" + search_method = _partialmethod( + type(self)._search_server_env, + field.name, + __name__=search_method_name, + ) + setattr(type(self), search_method_name, search_method) + field.search = search_method_name + field.store = False field.required = False field.copy = False diff --git a/server_environment/tests/test_server_environment_config.py b/server_environment/tests/test_server_environment_config.py index ba098b451..76732d5ab 100644 --- a/server_environment/tests/test_server_environment_config.py +++ b/server_environment/tests/test_server_environment_config.py @@ -93,3 +93,36 @@ def test_load_config(self): AssertionError, "can't write on readonly field 'password'" ): f.password = "newpass" + + def _setup_external_service(self): + from .models import ExternalService + + add_to_registry(self.registry, ExternalService) + self.registry._setup_models__(self.env.cr, ["external.service"]) + self.registry.init_models( + self.env.cr, ["external.service"], {"models_to_check": True} + ) + + def test_search_env_field(self): + """Env-computed fields can be used in a search domain. + + Regression test: since Odoo 19 the domain engine requires a + non-stored field to declare a ``search`` method, otherwise + searching on it raises ``ValueError: Cannot convert ... because + it is not stored``. + """ + self._setup_external_service() + model = self.env["external.service"] + ftp1 = model.create({"name": "ftp1", "description": "Description ftp1"}) + ftp2 = model.create({"name": "ftp2", "description": "Description ftp2"}) + ftp1.invalidate_recordset() + ftp2.invalidate_recordset() + with self.load_config(public=CONFIG): + # host is an env-computed (non-stored) field + self.assertEqual(ftp1.host, "sftp.example.com") + self.assertEqual(ftp2.host, "sftp2.example.com") + + self.assertEqual(model.search([("host", "=", "sftp.example.com")]), ftp1) + self.assertEqual(model.search([("host", "!=", "sftp.example.com")]), ftp2) + self.assertEqual(model.search([("host", "like", "sftp")]), ftp1 + ftp2) + self.assertFalse(model.search([("host", "=", "nope.example.com")]))