Coverage for ingadhoc-odoo-saas / saas_client / patch.py: 31%
76 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:22 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:22 +0000
1import configparser
2import logging
3import warnings
5from odoo.addons.base.models.ir_module import IrModuleModule
6from odoo.modules.module import Manifest
7from odoo.tools import config
9_logger = logging.getLogger(__name__)
10_original_init = Manifest.__init__
12_original__update_dependencies = IrModuleModule._update_dependencies
14# Based on https://github.com/OCA/server-tools/blob/19.0/module_change_auto_install
17def _get_modules_dict_auto_install_config(config_value):
18 """Given a configuration parameter name, return a dict of
19 {module_name: modules_list or False}
21 if the odoo.cfg file contains
23 [module_change_auto_install]
24 modules_enabled =
25 web_responsive:web,
26 base_technical_features:,
27 point_of_sale:sale/purchase,
28 account_usability
30 >>> split_strip('modules_enabled')
31 {
32 'web_responsive': ['web'],
33 'base_technical_features': [],
34 'point_of_sale': ['sale', 'purchase'],
35 'account_usability': False,
36 }
37 """
38 res = {}
39 config_value = (config_value or "").strip(" ,")
40 if config_value:
41 config_list = [x.strip() for x in config_value.split(",")]
42 for item in config_list:
43 if ":" in item:
44 res[item.split(":")[0]] = item.split(":")[1] and item.split(":")[1].split("/") or []
45 else:
46 res[item] = True
47 return res
50def _get_modules_auto_install_enabled_dict():
51 return _get_modules_dict_auto_install_config(config.get("module_change_auto_install.modules_enabled"))
54def _get_modules_auto_install_disabled_dict():
55 return _get_modules_dict_auto_install_config(config.get("module_change_auto_install.modules_disabled"))
58def _get_auto_install_flag(self):
59 modules_auto_install_enabled_dict = _get_modules_auto_install_enabled_dict()
60 modules_auto_install_disabled_dict = _get_modules_auto_install_disabled_dict()
61 auto_install = self._Manifest__manifest_cached["auto_install"]
62 module = self.name
64 if auto_install and module in modules_auto_install_disabled_dict.keys():
65 _logger.info(f"Module '{module}' has been marked as NOT auto installable.")
66 return False
68 if not auto_install and module in modules_auto_install_enabled_dict.keys():
69 specific_dependencies = modules_auto_install_enabled_dict.get(module)
70 if isinstance(specific_dependencies, bool):
71 # Classical case
72 _logger.info(f"Module '{module}' has been marked as auto installable.")
73 return set(self._Manifest__manifest_cached["depends"])
74 else:
75 if specific_dependencies:
76 _logger.info("ALL CASES.")
78 return set(specific_dependencies)
79 return auto_install
82def _get_modules_install_disabled():
83 modules_install_disabled = config.get("module_change_install.modules_disabled")
84 if not modules_install_disabled:
85 return []
86 return [x.strip() for x in modules_install_disabled.split(",")]
89def _patched_init(self, *, path: str, manifest_content: dict):
90 _original_init(self, path=path, manifest_content=manifest_content)
91 # Post-process before cached_property kicks in
92 self.auto_install = _get_auto_install_flag(self)
93 if "auto_install" in self._Manifest__manifest_cached:
94 self._Manifest__manifest_cached["auto_install"] = self.auto_install
97def _load_module_options(rcfile):
98 """Load custom [module_change_auto_install] and [module_change_install] sections into config."""
99 cp = configparser.ConfigParser()
100 cp.read([rcfile])
102 if cp.has_section("module_change_auto_install"): 102 ↛ 103line 102 didn't jump to line 103 because the condition on line 102 was never true
103 for key, value in cp.items("module_change_auto_install"):
104 # Store with prefix to avoid collisions
105 config[f"module_change_auto_install.{key}"] = value
106 _logger.debug("Loaded custom option %s=%s", key, value)
108 if cp.has_section("module_change_install"): 108 ↛ 109line 108 didn't jump to line 109 because the condition on line 108 was never true
109 for key, value in cp.items("module_change_install"):
110 # Store with prefix to avoid collisions
111 config[f"module_change_install.{key}"] = value
112 _logger.debug("Loaded custom option %s=%s", key, value)
115def _overload_update_dependencies(self, depends=None, auto_install_requirements=()):
116 """Para poder forzar modulos como no instalables tuvimos que hacer sto y no pudimos hacerlo en el patch.
117 Eso es porque odoo en el método update_list solo escribe estado "uninstallable" si está creando registro de
118 modulo pero no si ya existe y hace write. Esta fue una de las maneras mas faciles que encontramos de modificar
119 ese comportamiento para que si no es instalable lo marque como tal
120 Ademas no cambamos a instalalble = False en el patch porque eso hace que no funcione bien luego la
121 desinstalacion del modulo si estaba instalado. En cambio lo agregamos en module state en
122 get_modules_availabilty
123 Lo hacemos como monkey patch acá y no herencia normal porque si no al llamar desde línea de comando con -i o -u
124 se llama al update_list pero no entra en método modificado
125 Pudimos heredar sin problema update_list y que se llame de ambos lados, pero seguimos teniendo el problema de que
126 en la linea "if mod" encuentra modulo pero al no haber cambio ni si quiera hace write
127 Como lo estamos haciendo igual no está tan mal, el update_dependencies se llama en el mismo metodo update_state
128 """
129 _original__update_dependencies(self, depends=depends, auto_install_requirements=auto_install_requirements)
130 modules_install_disabled = _get_modules_install_disabled()
131 if self.state not in ["installed", "to upgrade"] and self.name in modules_install_disabled:
132 _logger.debug("Module '%s' has been marked as not installable." % self.name)
133 self.state = "uninstallable"
136def post_load():
137 _logger.info("Applying patch saas_client_adhoc related to module instalability")
138 Manifest.__init__ = _patched_init
139 rcfile = config.get("config")
140 if rcfile: 140 ↛ 142line 140 didn't jump to line 142 because the condition on line 140 was always true
141 _load_module_options(rcfile)
142 IrModuleModule._update_dependencies = _overload_update_dependencies
143 _logger.info("Applying patch to ignore 'cgi' deprecation warning")
144 warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*cgi.*deprecated.*")