From 6c0005bca2704307b68d52941e6bfe4ed84348e5 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Thu, 19 Feb 2026 12:27:35 -0500 Subject: [PATCH 01/11] [FIX] script execute first installation --- script/execute/execute.py | 2 +- script/todo/todo.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/execute/execute.py b/script/execute/execute.py index adb7c43..8ea3355 100644 --- a/script/execute/execute.py +++ b/script/execute/execute.py @@ -12,7 +12,7 @@ import time try: import humanize -except ImportError: +except ModuleNotFoundError as e: humanize = None cst_venv_erplibre = ".venv.erplibre" diff --git a/script/todo/todo.py b/script/todo/todo.py index 8ebb63f..7f3ffce 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -21,6 +21,7 @@ new_path = os.path.normpath( sys.path.append(new_path) from script.config import config_file +from script.execute import execute file_error_path = ".erplibre.error.txt" cst_venv_erplibre = ".venv.erplibre" @@ -56,7 +57,6 @@ try: # import rich import todo_upgrade from pykeepass import PyKeePass - except ModuleNotFoundError as e: humanize = None ENABLE_CRASH = True From 9cc3e88c16c6cc966e8e20eb98e91940fa49f635 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 28 Feb 2026 01:10:28 -0500 Subject: [PATCH 02/11] [FIX] todo database open dialog correction --- script/todo/todo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/todo/todo.py b/script/todo/todo.py index 7f3ffce..e634b5f 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -1041,7 +1041,7 @@ class TODO: if status == "1": file_name = status else: - file_name = self.open_file_image_db + file_name = self.open_file_image_db() database_name = input("💬 Database name : ") if not database_name: From dfc340d0c4ae42796a7e38197999099129800125 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 28 Feb 2026 01:35:32 -0500 Subject: [PATCH 03/11] [IMP] script todo: support run custom database --- script/todo/todo.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/script/todo/todo.py b/script/todo/todo.py index e634b5f..422ebb7 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -459,12 +459,20 @@ class TODO: lst_choice = self.config_file.get_config("instance") init_len = len(lst_choice) + # Support mobile ERPLibre if os.path.exists(MOBILE_HOME_PATH): dct_upgrade_odoo_database = { "prompt_description": "Mobile - Compile and run software", "callback": self.callback_make_mobile_home, } lst_choice.append(dct_upgrade_odoo_database) + + # Support custom database to execute + dct_upgrade_odoo_database = { + "prompt_description": "Choisir sa base de données", + "callback": self.callback_execute_custom_database, + } + lst_choice.insert(0, dct_upgrade_odoo_database) help_info = self.fill_help_info(lst_choice) while True: @@ -476,7 +484,7 @@ class TODO: cmd_no_found = True try: int_cmd = int(status) - if 0 < int_cmd <= init_len: + if 1 < int_cmd <= init_len: cmd_no_found = False status = click.confirm( "Voulez-vous une nouvelle instance?" @@ -487,7 +495,8 @@ class TODO: exec_run_db=True, ignore_makefile=not bool(status), ) - elif int_cmd <= len(lst_choice): + elif int_cmd <= len(lst_choice) or 1 == int_cmd: + cmd_no_found = False # Execute dynamic instance dct_instance = lst_choice[int_cmd - 1] self.execute_from_configuration( @@ -1033,6 +1042,10 @@ class TODO: if os.path.exists(poetry_lock): shutil.copy2(poetry_lock, path_file_odoo_lock) + def callback_execute_custom_database(self, dct_config): + database_name = self.select_database() + self.prompt_execute_selenium_and_run_db(database_name) + def restore_from_database(self, show_remote_list=True): path_image_db = os.path.join(os.getcwd(), "image_db") print("[1] By filename from image_db") From f0cc9b2eb91e145ebd164a969a4d4adc4d5ad310 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 28 Feb 2026 02:08:04 -0500 Subject: [PATCH 04/11] [IMP] script todo feature debug file todo.py --- script/todo/todo.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/script/todo/todo.py b/script/todo/todo.py index 422ebb7..ba6372d 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -600,6 +600,12 @@ class TODO: } lst_choice.append(dct_upgrade_odoo_database) + lst_choice.append( + { + "prompt_description": "Debug", + } + ) + help_info = self.fill_help_info(lst_choice) while True: @@ -608,8 +614,10 @@ class TODO: if status == "0": return False elif status == str(len(lst_choice)): - self.upgrade_module() + self.debug_ide() elif status == str(len(lst_choice) - 1): + self.upgrade_module() + elif status == str(len(lst_choice) - 2): self.open_shell_on_database() else: cmd_no_found = True @@ -791,6 +799,25 @@ class TODO: else: print("Commande non trouvée 🤖!") + def debug_ide(self): + lst_choice = [ + {"prompt_description": "Debug todo.py"}, + ] + help_info = self.fill_help_info(lst_choice) + + while True: + status = click.prompt(help_info) + print() + if status == "0": + return False + elif status == "1": + self.open_pycharm_file( + os.getcwd(), + os.path.join(os.getcwd(), "script/todo/todo.py"), + ) + else: + print("Commande non trouvée 🤖!") + def generate_config_from_backup(self): file_name = self.open_file_image_db() add_arg = f"--from_backup_name {file_name} --add_repo odoo18.0/addons/MathBenTech_development" @@ -994,6 +1021,17 @@ class TODO: new_window=True, ) + def open_pycharm_file(self, folder, filename): + cmd = "~/.local/share/JetBrains/Toolbox/scripts/pycharm" + # cmd = "/snap/bin/pycharm-community" + # if pycharm_arg: + # cmd += f" {pycharm_arg}" + if folder: + cmd += f" {folder}" + if filename: + cmd += f" --line 1 {filename}" + self.execute.exec_command_live(cmd, source_erplibre=False) + def upgrade_module(self): upgrade = todo_upgrade.TodoUpgrade(self) upgrade.execute_module_upgrade() From fe9a8c95ffe5332de4987dd012d31d2a39852eec Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 28 Feb 2026 02:14:38 -0500 Subject: [PATCH 05/11] [IMP] script todo: config setup queue_job --- script/todo/todo.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/script/todo/todo.py b/script/todo/todo.py index ba6372d..599f6e1 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -727,6 +727,7 @@ class TODO: {"prompt_description": "Generate from pre-configuration"}, {"prompt_description": "Generate from backup file"}, {"prompt_description": "Generate from database"}, + {"prompt_description": "Setup queue job for parallelism"}, ] help_info = self.fill_help_info(lst_choice) @@ -743,6 +744,8 @@ class TODO: self.generate_config_from_backup() elif status == "4": self.generate_config_from_database() + elif status == "5": + self.generate_config_queue_job() else: print("Commande non trouvée 🤖!") @@ -829,6 +832,14 @@ class TODO: self.generate_config(add_arg=str_arg) return False + def generate_config_queue_job(self): + cmd = "./script/config/setup_odoo_config_conf_devops.py" + self.execute.exec_command_live( + cmd, + source_erplibre=False, + single_source_erplibre=True, + ) + def select_database(self): cmd_server = f"./odoo_bin.sh db --list" status, lst_database = self.execute.exec_command_live( From 449224f91b03dead9bd52095be63ee12cf71c6c4 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 28 Feb 2026 03:24:22 -0500 Subject: [PATCH 06/11] [IMP] script todo: network option for ssh forwarding and performance --- script/todo/todo.py | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/script/todo/todo.py b/script/todo/todo.py index 599f6e1..9741962 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -208,6 +208,7 @@ class TODO: [6] Database - Outils sur les bases de données [7] Process - Outils sur les exécutions [8] Config - Traitement du fichier de configuration +[9] Réseau - Outil réseautique [0] Retour """ while True: @@ -247,6 +248,10 @@ class TODO: status = self.prompt_execute_config() if status is not False: return + elif status == "9": + status = self.prompt_execute_network() + if status is not False: + return else: print("Commande non trouvée 🤖!") @@ -749,6 +754,50 @@ class TODO: else: print("Commande non trouvée 🤖!") + def prompt_execute_network(self): + print("🤖 Outil réseautique!") + lst_choice = [ + {"prompt_description": "SSH port-forwarding"}, + {"prompt_description": "Network performance request per second"}, + ] + help_info = self.fill_help_info(lst_choice) + + while True: + status = click.prompt(help_info) + print() + if status == "0": + return False + elif status == "1": + self.generate_network_port_forwarding() + elif status == "2": + self.generate_network_performance_test() + else: + print("Commande non trouvée 🤖!") + + def generate_network_port_forwarding(self, add_arg=None): + # ssh -L local_port:localhost:remote_port SSH_connection + ssh_connection = click.prompt( + "SSH connection, check ~/.ssh/config or user@address" + ) + local_port = click.prompt("local port (8069)") + remote_port = click.prompt("remote port (8069)") + cmd = f"ssh -L {local_port}:localhost:{remote_port} {ssh_connection}" + self.execute.exec_command_live( + cmd, + source_erplibre=False, + single_source_erplibre=False, + ) + + def generate_network_performance_test(self, add_arg=None): + # ./script/performance/test_performance.sh + address = click.prompt("https address, like https://erplibre.com") + cmd = f"./script/performance/test_performance.sh {address}" + self.execute.exec_command_live( + cmd, + source_erplibre=False, + single_source_erplibre=True, + ) + def generate_config(self, add_arg=None): # Repeating to get all item before get group cmd = ( From 0a7bca4801f3db360470f43502bbc8066746327c Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sun, 22 Feb 2026 12:14:07 -0500 Subject: [PATCH 07/11] [UPD] script todo_upgrade support odoo --neutralize --- script/todo/todo_upgrade.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/script/todo/todo_upgrade.py b/script/todo/todo_upgrade.py index 9742be7..5a89e40 100755 --- a/script/todo/todo_upgrade.py +++ b/script/todo/todo_upgrade.py @@ -753,8 +753,12 @@ class TodoUpgrade: if do_copy: shutil.copy(self.file_path, image_db_file_path) + neutralize_arg = "" + if start_version >= 16 and do_neutralize: + neutralize_arg = " --neutralize" + status, cmd_executed = self.todo_upgrade_execute( - f"./script/database/db_restore.py --database {database_name} --image {file_name} --ignore_cache", + f"./script/database/db_restore.py --database {database_name} --image {file_name} --ignore_cache{neutralize_arg}", single_source_odoo=True, ) if not status: @@ -769,6 +773,16 @@ class TodoUpgrade: f"./script/addons/update_prod_to_dev.sh {database_name}", single_source_odoo=True, ) + + # TODO exécuter next line si status != 0 et log contient + # psycopg2.errors.UndefinedTable: relation "discuss_channel" does not exist + # LIGNE 1 : SELECT "discuss_channel"."id" FROM "discuss_channel" WHERE (... + # source ./.venv.odoo18.0_python3.12.10/bin/activate && cat script/postgresql/migration/fix_migration_postgresql_17_to_postgresql_18_module_mail_nov_2025.py | ./odoo18.0/odoo/odoo-bin shell -d ripbylop_stage_prod_17_nov_2025 + # psycopg2.errors.ForeignKeyViolation: insert or update on table "discuss_channel_member" violates foreign key constraint "discuss_channel_member_channel_id_fkey" + # DÉTAIL : Key (channel_id)=(20) is not present in table "discuss_channel". + # ./script/database/migrate/process_backup_file.py --path_backup_zip image_db/db.zip --path_output_zip image_db/dbFIX.zip --word_to_delete discuss_channel_channel_type_not_null + # Puis faire un retry de la commande, sinon rien + self.dct_progression["state_1_neutralize_database"] = True self.write_config() if not status: @@ -1888,6 +1902,10 @@ class TodoUpgrade: .strip() .lower() ) + + # psycopg2.errors.UndefinedTable: relation "discuss_channel" does not exist + # LIGNE 1 : SELECT "discuss_channel"."id" FROM "discuss_channel" WHERE (... + if wait_status == "1": return self.todo_upgrade_execute( cmd, From 88b5ae13165233ed1bd58a693b660c2dd526a441 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 16 Feb 2026 17:45:44 -0500 Subject: [PATCH 08/11] [UPD] manifest update with code_generator --- manifest/git_manifest_odoo18.0.xml | 62 ++++++++++++++++++------------ 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/manifest/git_manifest_odoo18.0.xml b/manifest/git_manifest_odoo18.0.xml index 8642137..6083d27 100644 --- a/manifest/git_manifest_odoo18.0.xml +++ b/manifest/git_manifest_odoo18.0.xml @@ -132,13 +132,7 @@ remote="OCA" groups="addons,odoo18.0" /> - + - - + + - + - + @@ -564,7 +570,7 @@ revision="18.0" path="odoo18.0/addons/OCA_partner-contact" remote="OCA" - groups="addons,odoo18.0" + groups="addons,odoo18.0,code_generator" /> - + Date: Mon, 16 Feb 2026 17:59:13 -0500 Subject: [PATCH 09/11] [FIX] TODO: config regenerate .repo/local_manifests/erplibre_manifest.xml --- script/todo/todo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/script/todo/todo.py b/script/todo/todo.py index 9741962..c54b115 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -801,6 +801,7 @@ class TODO: def generate_config(self, add_arg=None): # Repeating to get all item before get group cmd = ( + f"./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_OCA;" f"./script/git/git_repo_update_group.py;" f"./script/generate_config.sh" ) From 9499e19045982dffcaa823c39d54b1b3e4b8c248 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 16 Feb 2026 22:35:08 -0500 Subject: [PATCH 10/11] [IMP] script search class model: transmit information module to install --- script/code_generator/search_class_model.py | 25 ++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/script/code_generator/search_class_model.py b/script/code_generator/search_class_model.py index d20dfd6..8b4a928 100755 --- a/script/code_generator/search_class_model.py +++ b/script/code_generator/search_class_model.py @@ -239,14 +239,30 @@ def main(): lst_model_inherit_name = [] lst_search_target = ("_name",) dct_model = {} + lst_depends = [] lst_search_inherit_target = ("_inherit",) if config.with_inherit else [] # lst_py_file = glob.glob(os.path.join(config.directory, "***", "*.py")) lst_py_file = Path(config.directory).rglob("*.py") + directory = os.path.normpath(config.directory) for py_file in lst_py_file: - if py_file == "__init__.py": + if str(py_file).endswith("__init__.py"): continue + + # Extract depends from manifest + relative_file = str(py_file)[len(directory) + 1 :] + if relative_file == "__manifest__.py": + with open(py_file, "r", encoding="utf-8") as f: + source = f.read() + try: + manifest = ast.literal_eval(source) + except (ValueError, SyntaxError): + manifest = None + if manifest: + lst_depends = manifest.get("depends", []) + + # Extract models with open(py_file, "r") as source: f_lines = source.read() try: @@ -370,6 +386,9 @@ def main(): ) if value is not None: d[keyword.arg] = value + dct_data = {"model": dct_model} + if lst_depends: + dct_data["depends"] = lst_depends lst_model_name.sort() lst_model_inherit_name.sort() models_name = "; ".join(lst_model_name) @@ -386,9 +405,9 @@ def main(): print(models_inherit_name) else: if config.format_json: - output = json.dumps(dct_model, indent=4, sort_keys=True) + output = json.dumps(dct_data, indent=4, sort_keys=True) else: - output = json.dumps(dct_model) + output = json.dumps(dct_data) print(output) if config.template_dir: From dd11ed89902626f8f30c06fac1f9fdeaee8fce7c Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 2 Mar 2026 16:06:30 -0500 Subject: [PATCH 11/11] [IMP] todo: create backup command and improve restore database name --- script/todo/todo.py | 61 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/script/todo/todo.py b/script/todo/todo.py index c54b115..addb391 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -693,6 +693,7 @@ class TODO: "prompt_description": "Download database to create backup (.zip)" }, {"prompt_description": "Restore from backup (.zip)"}, + {"prompt_description": "Create backup (.zip)"}, ] help_info = self.fill_help_info(lst_choice) @@ -705,6 +706,8 @@ class TODO: self.download_database_backup_cli() elif status == "2": self.restore_from_database() + elif status == "3": + self.create_backup_from_database() else: print("Commande non trouvée 🤖!") @@ -1155,22 +1158,34 @@ class TODO: else: file_name = self.open_file_image_db() - database_name = input("💬 Database name : ") + default_database_name = file_name.replace(" ", "_") + if default_database_name.endswith(".zip"): + default_database_name = default_database_name[:-4] + + database_name = input( + f"💬 Database name (default={default_database_name}) : " + ) if not database_name: - _logger.error("Missing database name") - return + database_name = default_database_name + + status = ( + input("💬 Would you like to neutralize database (n/N)? ") + .strip() + .lower() + ) + is_neutralize = False + more_arg = "" + if status != "n": + more_arg = "--neutralize " + is_neutralize = True + database_name += "_neutralize" status, lst_output = self.execute.exec_command_live( - f"python3 ./script/database/db_restore.py -d {database_name} --ignore_cache --image {file_name}", + f"python3 ./script/database/db_restore.py -d {database_name} {more_arg}--ignore_cache --image {file_name}", return_status_and_output=True, single_source_erplibre=True, source_erplibre=False, ) - status = ( - input("💬 Would you like to neutralize database (y/Y)? ") - .strip() - .lower() - ) - if status == "y": + if is_neutralize: status, lst_output = self.execute.exec_command_live( f"./script/addons/update_prod_to_dev.sh {database_name}", return_status_and_output=True, @@ -1190,6 +1205,32 @@ class TODO: source_erplibre=False, ) + def create_backup_from_database(self, show_remote_list=True): + database_name = self.select_database() + str_arg = f"--database {database_name}" + + backup_name = input("💬 Backup name (default = name+date.zip) : ") + if not backup_name: + backup_name = ( + database_name + + "_" + + datetime.datetime.now().strftime("%Y-%m-%d_%Hh%Mm%Ss") + + ".zip" + ) + + if not backup_name.endswith(".zip"): + backup_name = backup_name + ".zip" + + print(backup_name) + + cmd = f"./odoo_bin.sh db --backup --database {database_name} --restore_image {backup_name}" + status, lst_output = self.execute.exec_command_live( + cmd, + return_status_and_output=True, + single_source_erplibre=True, + source_erplibre=False, + ) + def open_file_image_db(self): self.dir_path = "" path_image_db = os.path.join(os.getcwd(), "image_db")