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:37 +0000

1import configparser 

2import logging 

3import warnings 

4 

5from odoo.addons.base.models.ir_module import IrModuleModule 

6from odoo.modules.module import Manifest 

7from odoo.tools import config 

8 

9_logger = logging.getLogger(__name__) 

10_original_init = Manifest.__init__ 

11 

12_original__update_dependencies = IrModuleModule._update_dependencies 

13 

14# Based on https://github.com/OCA/server-tools/blob/19.0/module_change_auto_install 

15 

16 

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} 

20 

21 if the odoo.cfg file contains 

22 

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 

29 

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 

48 

49 

50def _get_modules_auto_install_enabled_dict(): 

51 return _get_modules_dict_auto_install_config(config.get("module_change_auto_install.modules_enabled")) 

52 

53 

54def _get_modules_auto_install_disabled_dict(): 

55 return _get_modules_dict_auto_install_config(config.get("module_change_auto_install.modules_disabled")) 

56 

57 

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 

63 

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 

67 

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.") 

77 

78 return set(specific_dependencies) 

79 return auto_install 

80 

81 

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(",")] 

87 

88 

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 

95 

96 

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]) 

101 

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) 

107 

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) 

113 

114 

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" 

134 

135 

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.*")