@@ -19,3 +19,142 @@ If you want to have a technical name to reference:
1919 _inherit = ["storage.backend", "server.env.techname.mixin"]
2020
2121 [...]
22+
23+ ## Restoring columns on uninstall
24+
25+ When ` server.env.mixin ` is bound to an existing model, the ORM drops the
26+ original stored columns for all env-managed fields. If the binding addon is
27+ later uninstalled, those columns must be recreated so the database remains
28+ usable.
29+
30+ Add an ` uninstall_hook ` to your addon and delegate to
31+ ` restore_env_managed_columns ` :
32+
33+ # your_addon/__init__.py
34+ from . import models
35+
36+ def uninstall_hook(env):
37+ env["server.env.mixin"].restore_env_managed_columns(
38+ "storage.backend",
39+ ["directory_path", "other_field"],
40+ )
41+
42+ # your_addon/__manifest__.py
43+ {
44+ ...
45+ "uninstall_hook": "uninstall_hook",
46+ }
47+
48+ The helper creates any missing columns (idempotent: safe to call multiple
49+ times) and repopulates them with each record's current effective value —
50+ whether that value came from an environment configuration file or from the
51+ stored default field (` x_<field>_env_default ` ).
52+
53+ The hook must run * before* the ORM extensions are removed, which is guaranteed
54+ by Odoo's uninstall sequence (hooks execute before ` Module.module_uninstall() ` ).
55+
56+ ### Handling required fields
57+
58+ If a restored column is ** required** (has a ` NOT NULL ` constraint) but has no
59+ effective value (missing from environment config and no default field set), the
60+ restoration will fail with a ` UserError ` .
61+
62+ ** Solution:** pass a ` field_defaults ` dictionary with fallback values:
63+
64+ def uninstall_hook(env):
65+ env["server.env.mixin"].restore_env_managed_columns(
66+ "ir.mail_server",
67+ ["smtp_host", "smtp_authentication"],
68+ field_defaults={
69+ "smtp_authentication": "login", # fallback for required field
70+ },
71+ )
72+
73+ The helper will use the fallback value if provided and the computed field value
74+ is empty. If no fallback is provided but a required field has no value, a
75+ ` UserError ` is raised with instructions on how to provide a ` field_defaults `
76+ parameter.
77+
78+ ## Migrating when dropping server_environment dependency
79+
80+ When refactoring an existing addon that embeds a ` server.env.mixin ` binding, you
81+ may want to extract the binding into a separate * glue* addon and drop the
82+ ` server_environment ` dependency from the original. This keeps the base addon
83+ lightweight while preserving server-environment features for those who install
84+ the glue addon.
85+
86+ ** Pattern:**
87+
88+ - ** Original addon (v1)** : depends on ` server_environment ` and binds the mixin
89+ directly in model code.
90+ - ** Refactored addon (v2)** : removes ` server_environment ` from dependencies,
91+ removes the mixin binding and the related ORM model inheritance.
92+ - ** New glue addon** (optional, same version): depends on both ` server_environment `
93+ and the original addon v2; re-adds the mixin binding in a separate module file.
94+
95+ ** Migration checklist:**
96+
97+ 1 . In the ** original addon's v2 ` __manifest__.py ` ** :
98+ - Remove ` "server_environment" ` from ` depends ` .
99+ - Remove the model file(s) that contained the mixin binding.
100+ - Update ` depends ` to add the new glue addon * if* the base addon still needs it
101+ (otherwise, make the glue addon optional for users who want env-binding).
102+
103+ 2 . In the ** original addon's v2 model code** :
104+ - Delete or simplify the model class that inherited from ` server.env.mixin ` .
105+ - If the model was only there for the binding, remove it entirely.
106+ - Restore the original field definitions (not as computed fields).
107+
108+ 3 . ** Create a migration script** (if needed) to restore columns * during the addon
109+ upgrade* , before the ORM model extensions are unloaded. Use a ` @post_load `
110+ hook or a dedicated migration script:
111+
112+ # migrations/18.0.1.0.0/post-restore-columns.py
113+ def migrate(cr, version):
114+ # Call the restoration logic while the v1 model is still active
115+ env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
116+ # If any field is required and may have no value in the environment,
117+ # provide a fallback via field_defaults
118+ env["server.env.mixin"].restore_env_managed_columns(
119+ "storage.backend",
120+ ["directory_path", "other_field"],
121+ field_defaults={
122+ "directory_path": "/tmp", # fallback for required field
123+ },
124+ )
125+
126+ 4 . ** Create the glue addon** with the model re-inheritance:
127+
128+ # your_addon_env/__init__.py
129+ from . import models
130+
131+ # your_addon_env/models/__init__.py
132+ from . import storage_backend
133+
134+ # your_addon_env/models/storage_backend.py
135+ class StorageBackend(models.Model):
136+ _name = "storage.backend"
137+ _inherit = ["storage.backend", "server.env.mixin"]
138+
139+ @property
140+ def _server_env_fields(self):
141+ return {"directory_path": {}}
142+
143+ # your_addon_env/__manifest__.py
144+ {
145+ "name": "Storage Backend – Server Environment",
146+ "version": "18.0.1.0.0",
147+ "depends": ["server_environment", "storage_backend"],
148+ "installable": True,
149+ }
150+
151+ ** Key points:**
152+
153+ - Column restoration must happen * during the addon upgrade* (step 3), not as an
154+ uninstall hook, because the original model binding is still active.
155+ - The ` restore_env_managed_columns ` helper is idempotent and safe to call even
156+ if columns already exist.
157+ - Users who do not need server environment features simply do * not* install the
158+ glue addon—the base addon continues to work with plain database columns.
159+ - Users who do need server environment can install both the base addon (v2+) and
160+ the glue addon (same version) to get the binding back.
0 commit comments