diff --git a/README.md b/README.md index b710ec29e..aa743aa17 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Available addons addon | version | maintainers | summary --- | --- | --- | --- [mail_environment](mail_environment/) | 19.0.1.0.0 | | Configure mail servers with server_environment_files -[server_environment](server_environment/) | 19.0.1.0.1 | | move some configurations out of the database +[server_environment](server_environment/) | 19.0.1.0.2 | | move some configurations out of the database [server_environment_ir_config_parameter](server_environment_ir_config_parameter/) | 19.0.1.0.0 | | Override System Parameters from server environment file [//]: # (end addons) diff --git a/server_environment/README.rst b/server_environment/README.rst index 5864aab90..33219af70 100644 --- a/server_environment/README.rst +++ b/server_environment/README.rst @@ -11,7 +11,7 @@ server configuration environment files !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:7b570ae57a4da880788d2bf7fa2ee6bb7c7b051eb1bb92364a566ca36e082c92 + !! source digest: sha256:a33c0b46ddf893a0c6c856bf944637008d0f08d60c7646e6c5e3bc13e9ccb847 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png @@ -214,6 +214,171 @@ If you want to have a technical name to reference: [...] +Restoring columns on uninstall +------------------------------ + +When ``server.env.mixin`` is bound to an existing model, the ORM drops +the original stored columns for all env-managed fields. If the binding +addon is later uninstalled, those columns must be recreated so the +database remains usable. + +Add an ``uninstall_hook`` to your addon and delegate to +``restore_env_managed_columns``: + +:: + + # your_addon/__init__.py + from ./hooks import uninstall_hook + # your_addon/hooks.py + from odoo.addons.server_environment import uninstall + + def uninstall_hook(env): + uninstall.restore_env_managed_columns( + env, + "storage.backend", + ["directory_path", "other_field"], + ) + + # your_addon/__manifest__.py + { + ... + "uninstall_hook": "uninstall_hook", + } + +The helper creates any missing columns (idempotent: safe to call +multiple times) and repopulates them with each record's current +effective value — whether that value came from an environment +configuration file or from the stored default field +(``x__env_default``). + +The hook must run *before* the ORM extensions are removed, which is +guaranteed by Odoo's uninstall sequence (hooks execute before +``Module.module_uninstall()``). + +Handling required fields +~~~~~~~~~~~~~~~~~~~~~~~~ + +If a restored column is **required** (has a ``NOT NULL`` constraint) but +has no effective value (missing from environment config and no default +field set), the restoration will fail with a ``UserError``. + +**Solution:** pass a ``field_defaults`` dictionary with fallback values: + +:: + + def uninstall_hook(env): + restore_env_managed_columns( + env, + "ir.mail_server", + ["smtp_host", "smtp_authentication"], + field_defaults={ + "smtp_authentication": "login", # fallback for required field + }, + ) + +The helper will use the fallback value if provided and the computed +field value is empty. If no fallback is provided but a required field +has no value, a ``UserError`` is raised with instructions on how to +provide a ``field_defaults`` parameter. + +Migrating when dropping server_environment dependency +----------------------------------------------------- + +When refactoring an existing addon that embeds a ``server.env.mixin`` +binding, you may want to extract the binding into a separate *glue* +addon and drop the ``server_environment`` dependency from the original. +This keeps the base addon lightweight while preserving +server-environment features for those who install the glue addon. + +**Pattern:** + +- **Original addon (v1)**: depends on ``server_environment`` and binds + the mixin directly in model code. +- **Refactored addon (v2)**: removes ``server_environment`` from + dependencies, removes the mixin binding and the related ORM model + inheritance. +- **New glue addon** (optional, same version): depends on both + ``server_environment`` and the original addon v2; re-adds the mixin + binding in a separate module file. + +**Migration checklist:** + +1. In the **original addon's v2 ``__manifest__.py``**: + + - Remove ``"server_environment"`` from ``depends``. + - Remove the model file(s) that contained the mixin binding. + - Update ``depends`` to add the new glue addon *if* the base addon + still needs it (otherwise, make the glue addon optional for users + who want env-binding). + +2. In the **original addon's v2 model code**: + + - Delete or simplify the model class that inherited from + ``server.env.mixin``. + - If the model was only there for the binding, remove it entirely. + - Restore the original field definitions (not as computed fields). + +3. **Create a migration script** (if needed) to restore columns *during + the addon upgrade*, before the ORM model extensions are unloaded. Use + a ``@post_load`` hook or a dedicated migration script: + + :: + + # migrations/18.0.1.0.0/post-restore-columns.py + def migrate(cr, version): + # Call the restoration logic while the v1 model is still active + env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) + # If any field is required and may have no value in the environment, + # provide a fallback via field_defaults + restore_env_managed_columns( + env, + "storage.backend", + ["directory_path", "other_field"], + field_defaults={ + "directory_path": "/tmp", # fallback for required field + }, + ) + +4. **Create the glue addon** with the model re-inheritance: + + :: + + # your_addon_env/__init__.py + from . import models + + # your_addon_env/models/__init__.py + from . import storage_backend + + # your_addon_env/models/storage_backend.py + class StorageBackend(models.Model): + _name = "storage.backend" + _inherit = ["storage.backend", "server.env.mixin"] + + @property + def _server_env_fields(self): + return {"directory_path": {}} + + # your_addon_env/__manifest__.py + { + "name": "Storage Backend – Server Environment", + "version": "18.0.1.0.0", + "depends": ["server_environment", "storage_backend"], + "installable": True, + } + +**Key points:** + +- Column restoration must happen *during the addon upgrade* (step 3), + not as an uninstall hook, because the original model binding is still + active. +- The ``restore_env_managed_columns`` helper is idempotent and safe to + call even if columns already exist. +- Users who do not need server environment features simply do *not* + install the glue addon—the base addon continues to work with plain + database columns. +- Users who do need server environment can install both the base addon + (v2+) and the glue addon (same version) to get the binding back. + Known issues / Roadmap ====================== diff --git a/server_environment/__init__.py b/server_environment/__init__.py index e59d3799a..5073e5a68 100644 --- a/server_environment/__init__.py +++ b/server_environment/__init__.py @@ -2,3 +2,4 @@ from . import models from . import server_env from .server_env import serv_config, setboolean +from . import uninstall diff --git a/server_environment/__manifest__.py b/server_environment/__manifest__.py index 180289d56..8e0b0ffb2 100644 --- a/server_environment/__manifest__.py +++ b/server_environment/__manifest__.py @@ -4,7 +4,7 @@ { "name": "server configuration environment files", - "version": "19.0.1.0.1", + "version": "19.0.1.0.2", "depends": ["base", "base_sparse_field"], "author": "Camptocamp,Odoo Community Association (OCA)", "summary": "move some configurations out of the database", diff --git a/server_environment/i18n/da.po b/server_environment/i18n/da.po index 2a9174a5a..742cc8726 100644 --- a/server_environment/i18n/da.po +++ b/server_environment/i18n/da.po @@ -9,19 +9,20 @@ msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-05-01 10:39+0000\n" -"PO-Revision-Date: 2017-05-01 10:39+0000\n" -"Last-Translator: OCA Transbot , 2017\n" +"PO-Revision-Date: 2026-07-01 16:46+0000\n" +"Last-Translator: Laura V \n" "Language-Team: Danish (https://www.transifex.com/oca/teams/23907/da/)\n" "Language: da\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.15.2\n" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__config msgid "Config" -msgstr "" +msgstr "Konfiguration" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__create_uid @@ -41,7 +42,7 @@ msgstr "Vist navn" #. module: server_environment #: model:ir.model,name:server_environment.model_server_config msgid "Display server configuration" -msgstr "" +msgstr "Vis serverkonfiguration" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__id @@ -61,524 +62,524 @@ msgstr "Sidst opdateret den" #. module: server_environment #: model:ir.model,name:server_environment.model_server_env_mixin msgid "Mixin to add server environment in existing models" -msgstr "" +msgstr "Mixin til at tilføje servermiljø i eksisterende modeller" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_env_mixin__server_env_defaults #: model:ir.model.fields,field_description:server_environment.field_server_env_techname_mixin__server_env_defaults msgid "Server Env Defaults" -msgstr "" +msgstr "Standardværdier for servermiljø" #. module: server_environment #: model:ir.actions.act_window,name:server_environment.server_env_act_show_config #: model:ir.ui.menu,name:server_environment.menu_server_show_config msgid "Server Environment" -msgstr "" +msgstr "Servermiljø" #. module: server_environment #: model:ir.model,name:server_environment.model_server_env_techname_mixin msgid "Server environment technical name" -msgstr "" +msgstr "Teknisk navn på servermiljø" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_env_techname_mixin__tech_name msgid "Tech Name" -msgstr "" +msgstr "Teknisk navn" #. module: server_environment #: model:ir.model.fields,help:server_environment.field_server_env_techname_mixin__tech_name msgid "Unique name for technical purposes. Eg: server env keys." -msgstr "" +msgstr "Unikt navn til tekniske formål. F.eks.: servernøgler til miljøet." #. module: server_environment #: model:res.groups,name:server_environment.has_server_configuration_access msgid "View Server Environment Configuration" -msgstr "" +msgstr "Vis konfiguration af servermiljø" #. module: server_environment #: model:ir.model.constraint,message:server_environment.constraint_server_env_techname_mixin_tech_name_uniq msgid "`tech_name` must be unique!" -msgstr "" +msgstr "`tech_name` skal være unik!" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_addons_path msgid "odoo | addons_path" -msgstr "" +msgstr "odoo | addons_path" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_admin_passwd msgid "odoo | admin_passwd" -msgstr "" +msgstr "odoo | admin_passwd" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_config msgid "odoo | config" -msgstr "" +msgstr "odoo | config" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_csv_internal_sep msgid "odoo | csv_internal_sep" -msgstr "" +msgstr "odoo | csv_internal_sep" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_data_dir msgid "odoo | data_dir" -msgstr "" +msgstr "odoo | data_dir" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_host msgid "odoo | db_host" -msgstr "" +msgstr "odoo | db_host" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_maxconn msgid "odoo | db_maxconn" -msgstr "" +msgstr "odoo | db_maxconn" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_maxconn_gevent msgid "odoo | db_maxconn_gevent" -msgstr "" +msgstr "odoo | db_maxconn_gevent" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_name msgid "odoo | db_name" -msgstr "" +msgstr "odoo | db_name" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_password msgid "odoo | db_password" -msgstr "" +msgstr "odoo | db_password" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_port msgid "odoo | db_port" -msgstr "" +msgstr "odoo | db_port" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_replica_host msgid "odoo | db_replica_host" -msgstr "" +msgstr "odoo | db_replica_host" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_replica_port msgid "odoo | db_replica_port" -msgstr "" +msgstr "odoo | db_replica_port" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_sslmode msgid "odoo | db_sslmode" -msgstr "" +msgstr "odoo | db_sslmode" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_template msgid "odoo | db_template" -msgstr "" +msgstr "odoo | db_template" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_user msgid "odoo | db_user" -msgstr "" +msgstr "odoo | db_user" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_dbfilter msgid "odoo | dbfilter" -msgstr "" +msgstr "odoo | dbfilter" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_demo msgid "odoo | demo" -msgstr "" +msgstr "odoo | demo" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_dev_mode msgid "odoo | dev_mode" -msgstr "" +msgstr "odoo | dev_mode" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_email_from msgid "odoo | email_from" -msgstr "" +msgstr "odoo | email_from" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_from_filter msgid "odoo | from_filter" -msgstr "" +msgstr "odoo | from_filter" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_geoip_city_db msgid "odoo | geoip_city_db" -msgstr "" +msgstr "odoo | geoip_city_db" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_geoip_country_db msgid "odoo | geoip_country_db" -msgstr "" +msgstr "odoo | geoip_country_db" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_gevent_port msgid "odoo | gevent_port" -msgstr "" +msgstr "odoo | gevent_port" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_http_enable msgid "odoo | http_enable" -msgstr "" +msgstr "odoo | http_enable" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_http_interface msgid "odoo | http_interface" -msgstr "" +msgstr "odoo | http_interface" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_http_port msgid "odoo | http_port" -msgstr "" +msgstr "odoo | http_port" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_import_partial msgid "odoo | import_partial" -msgstr "" +msgstr "odoo | import_partial" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_init msgid "odoo | init" -msgstr "" +msgstr "odoo | init" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_language msgid "odoo | language" -msgstr "" +msgstr "odoo | language" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_memory_hard msgid "odoo | limit_memory_hard" -msgstr "" +msgstr "odoo | limit_memory_hard" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_memory_hard_gevent msgid "odoo | limit_memory_hard_gevent" -msgstr "" +msgstr "odoo | limit_memory_hard_gevent" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_memory_soft msgid "odoo | limit_memory_soft" -msgstr "" +msgstr "odoo | limit_memory_soft" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_memory_soft_gevent msgid "odoo | limit_memory_soft_gevent" -msgstr "" +msgstr "odoo | limit_memory_soft_gevent" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_request msgid "odoo | limit_request" -msgstr "" +msgstr "odoo | limit_request" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_time_cpu msgid "odoo | limit_time_cpu" -msgstr "" +msgstr "odoo | limit_time_cpu" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_time_real msgid "odoo | limit_time_real" -msgstr "" +msgstr "odoo | limit_time_real" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_time_real_cron msgid "odoo | limit_time_real_cron" -msgstr "" +msgstr "odoo | limit_time_real_cron" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_time_worker_cron msgid "odoo | limit_time_worker_cron" -msgstr "" +msgstr "odoo | limit_time_worker_cron" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_list_db msgid "odoo | list_db" -msgstr "" +msgstr "odoo | list_db" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_log_db msgid "odoo | log_db" -msgstr "" +msgstr "odoo | log_db" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_log_db_level msgid "odoo | log_db_level" -msgstr "" +msgstr "odoo | log_db_level" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_log_handler msgid "odoo | log_handler" -msgstr "" +msgstr "odoo | log_handler" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_log_level msgid "odoo | log_level" -msgstr "" +msgstr "odoo | log_level" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_logfile msgid "odoo | logfile" -msgstr "" +msgstr "odoo | logfile" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_max_cron_threads msgid "odoo | max_cron_threads" -msgstr "" +msgstr "odoo | max_cron_threads" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_osv_memory_count_limit msgid "odoo | osv_memory_count_limit" -msgstr "" +msgstr "odoo | osv_memory_count_limit" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_overwrite_existing_translations msgid "odoo | overwrite_existing_translations" -msgstr "" +msgstr "odoo | overwrite_existing_translations" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_pg_path msgid "odoo | pg_path" -msgstr "" +msgstr "odoo | pg_path" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_pidfile msgid "odoo | pidfile" -msgstr "" +msgstr "odoo | pidfile" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_pre_upgrade_scripts msgid "odoo | pre_upgrade_scripts" -msgstr "" +msgstr "odoo | pre_upgrade_scripts" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_proxy_mode msgid "odoo | proxy_mode" -msgstr "" +msgstr "odoo | proxy_mode" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_publisher_warranty_url msgid "odoo | publisher_warranty_url" -msgstr "" +msgstr "odoo | publisher_warranty_url" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_reportgz msgid "odoo | reportgz" -msgstr "" +msgstr "odoo | reportgz" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_root_path msgid "odoo | root_path" -msgstr "" +msgstr "odoo | root_path" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_running_env msgid "odoo | running_env" -msgstr "" +msgstr "odoo | running_env" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_save msgid "odoo | save" -msgstr "" +msgstr "odoo | save" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_screencasts msgid "odoo | screencasts" -msgstr "" +msgstr "odoo | screencasts" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_screenshots msgid "odoo | screenshots" -msgstr "" +msgstr "odoo | screenshots" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_server_wide_modules msgid "odoo | server_wide_modules" -msgstr "" +msgstr "odoo | server_wide_modules" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_shell_interface msgid "odoo | shell_interface" -msgstr "" +msgstr "odoo | shell_interface" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_smtp_password msgid "odoo | smtp_password" -msgstr "" +msgstr "odoo | smtp_password" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_smtp_port msgid "odoo | smtp_port" -msgstr "" +msgstr "odoo | smtp_port" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_smtp_server msgid "odoo | smtp_server" -msgstr "" +msgstr "odoo | smtp_server" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_smtp_ssl msgid "odoo | smtp_ssl" -msgstr "" +msgstr "odoo | smtp_ssl" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_smtp_ssl_certificate_filename msgid "odoo | smtp_ssl_certificate_filename" -msgstr "" +msgstr "odoo | smtp_ssl_certificate_filename" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_smtp_ssl_private_key_filename msgid "odoo | smtp_ssl_private_key_filename" -msgstr "" +msgstr "odoo | smtp_ssl_private_key_filename" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_smtp_user msgid "odoo | smtp_user" -msgstr "" +msgstr "odoo | smtp_user" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_stop_after_init msgid "odoo | stop_after_init" -msgstr "" +msgstr "odoo | stop_after_init" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_syslog msgid "odoo | syslog" -msgstr "" +msgstr "odoo | syslog" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_test_enable msgid "odoo | test_enable" -msgstr "" +msgstr "odoo | test_enable" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_test_file msgid "odoo | test_file" -msgstr "" +msgstr "odoo | test_file" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_test_tags msgid "odoo | test_tags" -msgstr "" +msgstr "odoo | test_tags" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_transient_age_limit msgid "odoo | transient_age_limit" -msgstr "" +msgstr "odoo | transient_age_limit" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_translate_in msgid "odoo | translate_in" -msgstr "" +msgstr "odoo | translate_in" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_translate_modules msgid "odoo | translate_modules" -msgstr "" +msgstr "odoo | translate_modules" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_translate_out msgid "odoo | translate_out" -msgstr "" +msgstr "odoo | translate_out" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_unaccent msgid "odoo | unaccent" -msgstr "" +msgstr "odoo | unaccent" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_update msgid "odoo | update" -msgstr "" +msgstr "odoo | update" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_upgrade_path msgid "odoo | upgrade_path" -msgstr "" +msgstr "odoo | upgrade_path" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_websocket_keep_alive_timeout msgid "odoo | websocket_keep_alive_timeout" -msgstr "" +msgstr "odoo | websocket_keep_alive_timeout" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_websocket_rate_limit_burst msgid "odoo | websocket_rate_limit_burst" -msgstr "" +msgstr "odoo | websocket_rate_limit_burst" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_websocket_rate_limit_delay msgid "odoo | websocket_rate_limit_delay" -msgstr "" +msgstr "odoo | websocket_rate_limit_delay" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_without_demo msgid "odoo | without_demo" -msgstr "" +msgstr "odoo | without_demo" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_workers msgid "odoo | workers" -msgstr "" +msgstr "odoo | workers" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_x_sendfile msgid "odoo | x_sendfile" -msgstr "" +msgstr "odoo | x_sendfile" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__system_I_architecture msgid "system | architecture" -msgstr "" +msgstr "system | architecture" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__system_I_locale msgid "system | locale" -msgstr "" +msgstr "system | locale" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__system_I_lsb_release msgid "system | lsb_release" -msgstr "" +msgstr "system | lsb_release" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__system_I_odoo msgid "system | odoo" -msgstr "" +msgstr "system | odoo" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__system_I_os_name msgid "system | os_name" -msgstr "" +msgstr "system | os_name" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__system_I_platform msgid "system | platform" -msgstr "" +msgstr "system | platform" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__system_I_python msgid "system | python" -msgstr "" +msgstr "system | python" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__system_I_release msgid "system | release" -msgstr "" +msgstr "system | release" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__system_I_revision msgid "system | revision" -msgstr "" +msgstr "system | revision" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__system_I_version msgid "system | version" -msgstr "" +msgstr "system | version" #~ msgid "Last Modified on" #~ msgstr "Sidst ændret den" diff --git a/server_environment/i18n/sv.po b/server_environment/i18n/sv.po index 92aee87ca..f0598420d 100644 --- a/server_environment/i18n/sv.po +++ b/server_environment/i18n/sv.po @@ -9,15 +9,15 @@ msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-05-01 10:39+0000\n" -"PO-Revision-Date: 2024-06-12 11:35+0000\n" -"Last-Translator: jakobkrabbe \n" +"PO-Revision-Date: 2026-07-01 16:46+0000\n" +"Last-Translator: Laura V \n" "Language-Team: Swedish (https://www.transifex.com/oca/teams/23907/sv/)\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.17\n" +"X-Generator: Weblate 5.15.2\n" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__config @@ -159,12 +159,12 @@ msgstr "odoo | db_port" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_replica_host msgid "odoo | db_replica_host" -msgstr "" +msgstr "odoo | db_replica_host" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_replica_port msgid "odoo | db_replica_port" -msgstr "" +msgstr "odoo | db_replica_port" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_db_sslmode @@ -259,7 +259,7 @@ msgstr "odoo | limit_memory_hard" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_memory_hard_gevent msgid "odoo | limit_memory_hard_gevent" -msgstr "" +msgstr "odoo | limit_memory_hard_gevent" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_memory_soft @@ -269,7 +269,7 @@ msgstr "odoo | limit_memory_soft" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_memory_soft_gevent msgid "odoo | limit_memory_soft_gevent" -msgstr "" +msgstr "odoo | limit_memory_soft_gevent" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_request @@ -294,7 +294,7 @@ msgstr "odoo | limit_time_real_cron" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_limit_time_worker_cron msgid "odoo | limit_time_worker_cron" -msgstr "" +msgstr "odoo | limit_time_worker_cron" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_list_db @@ -354,7 +354,7 @@ msgstr "odoo | pidfile" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_pre_upgrade_scripts msgid "odoo | pre_upgrade_scripts" -msgstr "" +msgstr "odoo | pre_upgrade_scripts" #. module: server_environment #: model:ir.model.fields,field_description:server_environment.field_server_config__odoo_I_proxy_mode diff --git a/server_environment/readme/USAGE.md b/server_environment/readme/USAGE.md index ed811ae09..22b99678d 100644 --- a/server_environment/readme/USAGE.md +++ b/server_environment/readme/USAGE.md @@ -19,3 +19,147 @@ If you want to have a technical name to reference: _inherit = ["storage.backend", "server.env.techname.mixin"] [...] + +## Restoring columns on uninstall + +When `server.env.mixin` is bound to an existing model, the ORM drops the +original stored columns for all env-managed fields. If the binding addon is +later uninstalled, those columns must be recreated so the database remains +usable. + +Add an `uninstall_hook` to your addon and delegate to +`restore_env_managed_columns`: + + # your_addon/__init__.py + from ./hooks import uninstall_hook + # your_addon/hooks.py + from odoo.addons.server_environment import uninstall + + def uninstall_hook(env): + uninstall.restore_env_managed_columns( + env, + "storage.backend", + ["directory_path", "other_field"], + ) + + # your_addon/__manifest__.py + { + ... + "uninstall_hook": "uninstall_hook", + } + +The helper creates any missing columns (idempotent: safe to call multiple +times) and repopulates them with each record's current effective value — +whether that value came from an environment configuration file or from the +stored default field (`x__env_default`). + +The hook must run *before* the ORM extensions are removed, which is guaranteed +by Odoo's uninstall sequence (hooks execute before `Module.module_uninstall()`). + +### Handling required fields + +If a restored column is **required** (has a `NOT NULL` constraint) but has no +effective value (missing from environment config and no default field set), the +restoration will fail with a `UserError`. + +**Solution:** pass a `field_defaults` dictionary with fallback values: + + def uninstall_hook(env): + restore_env_managed_columns( + env, + "ir.mail_server", + ["smtp_host", "smtp_authentication"], + field_defaults={ + "smtp_authentication": "login", # fallback for required field + }, + ) + +The helper will use the fallback value if provided and the computed field value +is empty. If no fallback is provided but a required field has no value, a +`UserError` is raised with instructions on how to provide a `field_defaults` +parameter. + +## Migrating when dropping server_environment dependency + +When refactoring an existing addon that embeds a `server.env.mixin` binding, you +may want to extract the binding into a separate *glue* addon and drop the +`server_environment` dependency from the original. This keeps the base addon +lightweight while preserving server-environment features for those who install +the glue addon. + +**Pattern:** + +- **Original addon (v1)**: depends on `server_environment` and binds the mixin + directly in model code. +- **Refactored addon (v2)**: removes `server_environment` from dependencies, + removes the mixin binding and the related ORM model inheritance. +- **New glue addon** (optional, same version): depends on both `server_environment` + and the original addon v2; re-adds the mixin binding in a separate module file. + +**Migration checklist:** + +1. In the **original addon's v2 `__manifest__.py`**: + - Remove `"server_environment"` from `depends`. + - Remove the model file(s) that contained the mixin binding. + - Update `depends` to add the new glue addon *if* the base addon still needs it + (otherwise, make the glue addon optional for users who want env-binding). + +2. In the **original addon's v2 model code**: + - Delete or simplify the model class that inherited from `server.env.mixin`. + - If the model was only there for the binding, remove it entirely. + - Restore the original field definitions (not as computed fields). + +3. **Create a migration script** (if needed) to restore columns *during the addon + upgrade*, before the ORM model extensions are unloaded. Use a `@post_load` + hook or a dedicated migration script: + + # migrations/18.0.1.0.0/post-restore-columns.py + def migrate(cr, version): + # Call the restoration logic while the v1 model is still active + env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) + # If any field is required and may have no value in the environment, + # provide a fallback via field_defaults + restore_env_managed_columns( + env, + "storage.backend", + ["directory_path", "other_field"], + field_defaults={ + "directory_path": "/tmp", # fallback for required field + }, + ) + +4. **Create the glue addon** with the model re-inheritance: + + # your_addon_env/__init__.py + from . import models + + # your_addon_env/models/__init__.py + from . import storage_backend + + # your_addon_env/models/storage_backend.py + class StorageBackend(models.Model): + _name = "storage.backend" + _inherit = ["storage.backend", "server.env.mixin"] + + @property + def _server_env_fields(self): + return {"directory_path": {}} + + # your_addon_env/__manifest__.py + { + "name": "Storage Backend – Server Environment", + "version": "18.0.1.0.0", + "depends": ["server_environment", "storage_backend"], + "installable": True, + } + +**Key points:** + +- Column restoration must happen *during the addon upgrade* (step 3), not as an + uninstall hook, because the original model binding is still active. +- The `restore_env_managed_columns` helper is idempotent and safe to call even + if columns already exist. +- Users who do not need server environment features simply do *not* install the + glue addon—the base addon continues to work with plain database columns. +- Users who do need server environment can install both the base addon (v2+) and + the glue addon (same version) to get the binding back. diff --git a/server_environment/static/description/index.html b/server_environment/static/description/index.html index 2c80792f4..b751235c0 100644 --- a/server_environment/static/description/index.html +++ b/server_environment/static/description/index.html @@ -372,7 +372,7 @@

server configuration environment files

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:7b570ae57a4da880788d2bf7fa2ee6bb7c7b051eb1bb92364a566ca36e082c92 +!! source digest: sha256:a33c0b46ddf893a0c6c856bf944637008d0f08d60c7646e6c5e3bc13e9ccb847 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Production/Stable License: LGPL-3 OCA/server-env Translate me on Weblate Try me on Runboat

This module provides a way to define an environment in the main Odoo @@ -398,13 +398,20 @@

server configuration environment files

  • Server environment integration
  • -
  • Usage
  • -
  • Known issues / Roadmap
  • -
  • Bug Tracker
  • -
  • Credits @@ -544,9 +551,165 @@

    Usage

    [...] +
    +

    Restoring columns on uninstall

    +

    When server.env.mixin is bound to an existing model, the ORM drops +the original stored columns for all env-managed fields. If the binding +addon is later uninstalled, those columns must be recreated so the +database remains usable.

    +

    Add an uninstall_hook to your addon and delegate to +restore_env_managed_columns:

    +
    +# your_addon/__init__.py
    +from ./hooks import uninstall_hook
    +# your_addon/hooks.py
    +from odoo.addons.server_environment import uninstall
    +
    +def uninstall_hook(env):
    +    uninstall.restore_env_managed_columns(
    +        env,
    +        "storage.backend",
    +        ["directory_path", "other_field"],
    +    )
    +
    +# your_addon/__manifest__.py
    +{
    +    ...
    +    "uninstall_hook": "uninstall_hook",
    +}
    +
    +

    The helper creates any missing columns (idempotent: safe to call +multiple times) and repopulates them with each record’s current +effective value — whether that value came from an environment +configuration file or from the stored default field +(x_<field>_env_default).

    +

    The hook must run before the ORM extensions are removed, which is +guaranteed by Odoo’s uninstall sequence (hooks execute before +Module.module_uninstall()).

    +
    +

    Handling required fields

    +

    If a restored column is required (has a NOT NULL constraint) but +has no effective value (missing from environment config and no default +field set), the restoration will fail with a UserError.

    +

    Solution: pass a field_defaults dictionary with fallback values:

    +
    +def uninstall_hook(env):
    +    restore_env_managed_columns(
    +        env,
    +        "ir.mail_server",
    +        ["smtp_host", "smtp_authentication"],
    +        field_defaults={
    +            "smtp_authentication": "login",  # fallback for required field
    +        },
    +    )
    +
    +

    The helper will use the fallback value if provided and the computed +field value is empty. If no fallback is provided but a required field +has no value, a UserError is raised with instructions on how to +provide a field_defaults parameter.

    +
    +
    +
    +

    Migrating when dropping server_environment dependency

    +

    When refactoring an existing addon that embeds a server.env.mixin +binding, you may want to extract the binding into a separate glue +addon and drop the server_environment dependency from the original. +This keeps the base addon lightweight while preserving +server-environment features for those who install the glue addon.

    +

    Pattern:

    +
      +
    • Original addon (v1): depends on server_environment and binds +the mixin directly in model code.
    • +
    • Refactored addon (v2): removes server_environment from +dependencies, removes the mixin binding and the related ORM model +inheritance.
    • +
    • New glue addon (optional, same version): depends on both +server_environment and the original addon v2; re-adds the mixin +binding in a separate module file.
    • +
    +

    Migration checklist:

    +
      +
    1. In the original addon’s v2 ``__manifest__.py``:

      +
        +
      • Remove "server_environment" from depends.
      • +
      • Remove the model file(s) that contained the mixin binding.
      • +
      • Update depends to add the new glue addon if the base addon +still needs it (otherwise, make the glue addon optional for users +who want env-binding).
      • +
      +
    2. +
    3. In the original addon’s v2 model code:

      +
        +
      • Delete or simplify the model class that inherited from +server.env.mixin.
      • +
      • If the model was only there for the binding, remove it entirely.
      • +
      • Restore the original field definitions (not as computed fields).
      • +
      +
    4. +
    5. Create a migration script (if needed) to restore columns during +the addon upgrade, before the ORM model extensions are unloaded. Use +a @post_load hook or a dedicated migration script:

      +
      +# migrations/18.0.1.0.0/post-restore-columns.py
      +def migrate(cr, version):
      +    # Call the restoration logic while the v1 model is still active
      +    env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
      +    # If any field is required and may have no value in the environment,
      +    # provide a fallback via field_defaults
      +    restore_env_managed_columns(
      +        env,
      +        "storage.backend",
      +        ["directory_path", "other_field"],
      +        field_defaults={
      +            "directory_path": "/tmp",  # fallback for required field
      +        },
      +    )
      +
      +
    6. +
    7. Create the glue addon with the model re-inheritance:

      +
      +# your_addon_env/__init__.py
      +from . import models
      +
      +# your_addon_env/models/__init__.py
      +from . import storage_backend
      +
      +# your_addon_env/models/storage_backend.py
      +class StorageBackend(models.Model):
      +    _name = "storage.backend"
      +    _inherit = ["storage.backend", "server.env.mixin"]
      +
      +    @property
      +    def _server_env_fields(self):
      +        return {"directory_path": {}}
      +
      +# your_addon_env/__manifest__.py
      +{
      +    "name": "Storage Backend – Server Environment",
      +    "version": "18.0.1.0.0",
      +    "depends": ["server_environment", "storage_backend"],
      +    "installable": True,
      +}
      +
      +
    8. +
    +

    Key points:

    +
      +
    • Column restoration must happen during the addon upgrade (step 3), +not as an uninstall hook, because the original model binding is still +active.
    • +
    • The restore_env_managed_columns helper is idempotent and safe to +call even if columns already exist.
    • +
    • Users who do not need server environment features simply do not +install the glue addon—the base addon continues to work with plain +database columns.
    • +
    • Users who do need server environment can install both the base addon +(v2+) and the glue addon (same version) to get the binding back.
    • +
    +
    -

    Known issues / Roadmap

    +

    Known issues / Roadmap

    • it is not possible to set the environment from the command line. A configuration file must be used.
    • @@ -558,7 +721,7 @@

      Known issues / Roadmap

    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -566,15 +729,15 @@

    Bug Tracker

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • Camptocamp
    -

    Contributors

    +

    Contributors

    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association diff --git a/server_environment/tests/test_environment_variable.py b/server_environment/tests/test_environment_variable.py index 74df177e4..5568c525b 100644 --- a/server_environment/tests/test_environment_variable.py +++ b/server_environment/tests/test_environment_variable.py @@ -1,4 +1,3 @@ -# Copyright 2018 Camptocamp (https://www.camptocamp.com). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) from unittest.mock import patch diff --git a/server_environment/tests/test_server_environment.py b/server_environment/tests/test_server_environment.py index 5ecdec43b..417bb2099 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,17 @@ 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. + with self.assertLogs( + "odoo.addons.server_environment.uninstall", level="WARNING" + ): + 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": ""}`` + """ + model = env[model_name] + cr = env.cr + field_defaults = field_defaults or {} + + for field_name in field_names: + field = model._fields.get(field_name) + if field is None: + _logger.warning( + "restore_env_managed_columns: field %r not found on %s, skipping", + field_name, + model_name, + ) + continue + column_type = field.column_type + if column_type is None: + _logger.warning( + "restore_env_managed_columns: " + "field %r on %s has no SQL column type, skipping", + field_name, + model_name, + ) + continue + table = model._table + if not sql.column_exists(cr, table, field_name): + sql.create_column(cr, table, field_name, column_type[1], field.string) + _logger.info( + "restore_env_managed_columns: created column %s.%s (%s)", + table, + field_name, + column_type[1], + ) + # Repopulate every existing record with the current computed value. + # The hook runs while the ORM extensions are still active, so the + # env-computed field is still readable via the normal accessor. + for record in model.search([]): + value = record[field_name] + # The ORM returns False for NULL on non-boolean fields; map + # that back to None so psycopg2 writes a proper SQL NULL. + if value is False and field.type != "boolean": + value = None + elif value == "": + value = None + + # Try to get a default value if we have None. + # Note: field.required is False after mixin transformation, + # so we apply defaults for all None values when available. + if value is None: + if field_name in field_defaults: + value = field_defaults[field_name] + elif field_name in model.default_get([field_name]): + value = model.default_get([field_name])[field_name] + + cr.execute( + SQL( + "UPDATE %s SET %s = %s WHERE id = %s", + SQL.identifier(table), + SQL.identifier(field_name), + value, + record.id, + ) + )