[FIX] migration: tell modules with no code from those to uninstall
Uninstalling before the 13->14 bump asked for 15 modules; 12 of them no longer exist in the 13.0 addons path. check_addons_exist.py rejects the command on the first missing name, so the uninstall aborted whole — and the 3 modules that WERE there stayed installed, blocking the bump for a reason that had nothing to do with them. The list is now split before the script is called, using check_addons_exist which was already there and simply never consulted at this point. « Missing » means the addons path has no code for it, not that the database lacks it — the distinction is the whole point, since Odoo cannot uninstall a module whose code is gone. When some are missing, a choice is offered rather than a failure: [1] uninstall the present ones, skip the missing (default) [2] try the whole list anyway (it will fail) [3] uninstall nothing, continue Only what is really uninstalled is subtracted from the per-version module list, so a module left in place stays counted as installed — which it is. The missing names are written to the progression comments, so what was skipped is still on record afterwards. Verified on the exact list from the failure: 3 present and 12 missing, option 1 issuing a command with only the 3 and leaving the 12 counted as installed, option 2 sending all 15, option 3 running nothing. Also swapped entries 4 and 5 of the Deploy menu — QEMU/KVM now sits at [4], NTFY at [5]. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d40e6aa011
commit
7d96b56fb4
3 changed files with 110 additions and 5 deletions
|
|
@ -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 !"))
|
||||
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
Loading…
Reference in a new issue