Maintainers
+Maintainers
This module is maintained by the OCA.
diff --git a/server_environment/tests/test_server_environment.py b/server_environment/tests/test_server_environment.py
index 9f605942a..bf3ce0c8a 100644
--- a/server_environment/tests/test_server_environment.py
+++ b/server_environment/tests/test_server_environment.py
@@ -6,6 +6,8 @@
from odoo.tools.config import config as odoo_config
+from odoo.addons.server_environment.uninstall import restore_env_managed_columns
+
from .. import server_env
from . import common
@@ -88,3 +90,14 @@ def test_default_hidden_password(self):
self.assertIn("odoo_I_db_password", defaults)
self.assertIn("odoo_I_smtp_password", defaults)
self.assertIn("outgoing_mail_provider_promail_I_smtp_pass", defaults)
+
+ def test_restore_env_managed_columns_unknown_field(self):
+ """Helper gracefully skips a field that doesn't exist on the model."""
+ # Must not raise even when the field name doesn't exist.
+ restore_env_managed_columns(
+ self.env, "res.partner", ["__nonexistent_field_xyz__"]
+ )
+
+ def test_restore_env_managed_columns_no_fields(self):
+ """Helper is a no-op when given an empty field list."""
+ restore_env_managed_columns(self.env, "res.partner", [])
diff --git a/server_environment/uninstall.py b/server_environment/uninstall.py
new file mode 100644
index 000000000..ba8a54268
--- /dev/null
+++ b/server_environment/uninstall.py
@@ -0,0 +1,98 @@
+# Copyright 2026 Camptocamp (https://www.camptocamp.com)
+import logging
+
+from odoo.tools import SQL, sql
+
+_logger = logging.getLogger(__name__)
+
+
+def restore_env_managed_columns(env, model_name, field_names, field_defaults=None):
+ """Restore database columns for fields formerly managed via server.env.mixin.
+
+ When an addon binds ``server.env.mixin`` to an existing model, the ORM
+ drops the original stored columns. Call this helper from an
+ ``uninstall_hook`` so those columns are recreated and repopulated
+ with their current effective values before the addon is removed.
+
+ The hook must run *while* the module's ORM extensions are still active
+ (guaranteed by Odoo's uninstall sequence: hooks execute before
+ ``Module.module_uninstall()``), so the env-computed fields are still
+ readable and their values can be written back to freshly created columns.
+
+ The operation is idempotent: calling it multiple times will not fail.
+
+ **Defaults:** If a restored field value is NULL/empty, the helper will
+ use the fallback from ``field_defaults`` (if provided) or the field's
+ ORM-level default (if defined).
+
+ Note: ``field.required`` is set to False by the mixin, so we cannot detect
+ which fields are required. Provide explicit ``field_defaults`` for fields
+ that must have values.
+
+ :param str model_name: dotted model name, e.g. ``"ir.mail_server"``
+ :param field_names: iterable of field names whose columns to restore
+ :param dict field_defaults: optional mapping of field name to fallback
+ value used when restoring a column that has no effective env-computed
+ value, e.g. ``{"smtp_authentication": "



