diff --git a/script/todo/todo.py b/script/todo/todo.py index 948e7fe..7c00436 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -879,12 +879,12 @@ class TODO: {"prompt_description": t("SSH (remote host)...")}, { "prompt_description": t( - "Deploy - Install NTFY notification server" + "QEMU/KVM - Deploy an Ubuntu VM (libvirt)" ) }, { "prompt_description": t( - "QEMU/KVM - Deploy an Ubuntu VM (libvirt)" + "Deploy - Install NTFY notification server" ) }, ] @@ -902,9 +902,9 @@ class TODO: elif status == "3": self.prompt_execute_deploy_ssh() elif status == "4": - self._deploy_ntfy_server() - elif status == "5": self.prompt_execute_qemu() + elif status == "5": + self._deploy_ntfy_server() else: print(t("Command not found !")) diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index fedf14f..ec612b4 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -1403,6 +1403,43 @@ TRANSLATIONS = { "fr": "🚚 Interface de la migration Odoo", "en": "🚚 Odoo migration interface", }, + "modules of the list have no code in the active Odoo:": { + "fr": "modules de la liste n'ont plus de code dans l'Odoo actif :", + "en": "modules of the list have no code in the active Odoo:", + }, + "Odoo cannot uninstall a module whose code is gone;": { + "fr": "Odoo ne peut pas désinstaller un module dont le code a" + " disparu ;", + "en": "Odoo cannot uninstall a module whose code is gone;", + }, + "one of them fails the whole uninstall command.": { + "fr": "un seul d'entre eux fait échouer toute la désinstallation.", + "en": "one of them fails the whole uninstall command.", + }, + "are present and can be uninstalled:": { + "fr": "sont présents et peuvent être désinstallés :", + "en": "are present and can be uninstalled:", + }, + "No module of the list is present.": { + "fr": "Aucun module de la liste n'est présent.", + "en": "No module of the list is present.", + }, + "Uninstall the present ones, skip the missing": { + "fr": "Désinstaller les présents, ignorer les manquants", + "en": "Uninstall the present ones, skip the missing", + }, + "Try the whole list anyway (it will fail)": { + "fr": "Tout tenter quand même (ça échouera)", + "en": "Try the whole list anyway (it will fail)", + }, + "Uninstall nothing, continue": { + "fr": "Ne rien désinstaller, continuer", + "en": "Uninstall nothing, continue", + }, + "Nothing uninstalled.": { + "fr": "Rien de désinstallé.", + "en": "Nothing uninstalled.", + }, "No migration in progress to resume.": { "fr": "Aucune migration en cours à reprendre.", "en": "No migration in progress to resume.", diff --git a/script/todo/todo_upgrade.py b/script/todo/todo_upgrade.py index 8203a7c..b44db34 100755 --- a/script/todo/todo_upgrade.py +++ b/script/todo/todo_upgrade.py @@ -2264,18 +2264,86 @@ class TodoUpgrade: lst_detail.append((module_name, reason, file_path)) return lst_module, lst_detail + def split_present_missing(self, lst_module): + """Split a module list into (present, missing) against the ACTIVE code. + + « Missing » means the addons path no longer holds the module — not + that it is absent from the database. The distinction matters because + check_addons_exist.py refuses the WHOLE uninstall for a single missing + name, so the modules that ARE there never get uninstalled either. + """ + lst_missing, _lst_duplicate = self.check_addons_exist(lst_module) + set_missing = set(lst_missing or []) + return ( + [name for name in lst_module if name not in set_missing], + [name for name in lst_module if name in set_missing], + ) + + def prompt_uninstall_missing(self, lst_present, lst_missing): + """Ask what to do when part of the list has no code left. + Returns the list to actually uninstall (possibly empty).""" + print() + print( + f"⚠️ {len(lst_missing)} " + f"{t('modules of the list have no code in the active Odoo:')}" + ) + for name in lst_missing: + print(f" {name}") + print(f" {t('Odoo cannot uninstall a module whose code is gone;')}") + print(f" {t('one of them fails the whole uninstall command.')}") + print() + if lst_present: + print( + f" {len(lst_present)} " + f"{t('are present and can be uninstalled:')}" + ) + for name in lst_present: + print(f" {name}") + else: + print(f" {t('No module of the list is present.')}") + print() + if lst_present: + print( + f" [1] {t('Uninstall the present ones, skip the missing')}" + " *" + ) + print(f" [2] {t('Try the whole list anyway (it will fail)')}") + print(f" [3] {t('Uninstall nothing, continue')}") + answer = input(f"💬 {t('Your choice')} : ").strip() + if answer == "2": + return lst_present + lst_missing + if answer == "3" or not lst_present: + return [] + return lst_present + def uninstall_from_database( self, lst_module_to_uninstall, database_name, actual_version ): if not lst_module_to_uninstall: return + # Sort out what the active code still holds BEFORE calling the script: + # it aborts on the first missing name and takes the rest down with it. + lst_present, lst_missing = self.split_present_missing( + lst_module_to_uninstall + ) + if lst_missing: + self.add_comment_progression( + "uninstall - no code for: " + ", ".join(lst_missing) + ) + lst_module_to_uninstall = self.prompt_uninstall_missing( + lst_present, lst_missing + ) + if not lst_module_to_uninstall: + print(f"⏭ {t('Nothing uninstalled.')}") + return uninstall_module = ",".join(lst_module_to_uninstall) self.todo_upgrade_execute( f"./script/addons/uninstall_addons.sh {database_name} {uninstall_module}", single_source_odoo=True, ) - # Update list installed module + # Update list installed module — only what was REALLY uninstalled, so + # a module left in place stays counted as installed. self.dct_module_per_version[actual_version] = sorted( list( set(self.dct_module_per_version[actual_version])