[ADD] migration: offer to remove the themes before the first bump
A theme carries view copies and SCSS through every version bump, and a bump can rename what they rely on. Removing it first drops a whole family of failures, and it can be put back afterwards. Asked in step 1, before OpenUpgrade runs, and only when a theme is actually installed. Default is no: removing a theme changes how a site looks, and a migration does not decide that for its owner. theme_default is not offered — it IS the absence of a theme. --- FR --- [ADD] migration : proposer de retirer les thèmes avant le premier palier Un thème traîne des copies de vues et des SCSS à travers chaque palier de version, et un palier peut renommer ce dont ils dépendent. Le retirer d'abord enlève une famille entière de pannes, et se refait ensuite. Posée à l'étape 1, avant OpenUpgrade, et seulement s'il y a vraiment un thème installé. Par défaut non : retirer un thème change l'apparence d'un site, et ce n'est pas à une migration de trancher cela à la place de son propriétaire. theme_default n'est pas proposé — il EST l'absence de thème. Assisted-by: Claude Opus 5 (cherry picked from commit dcbee87a6b920408c315f7354a096b0ecfabcf06)
This commit is contained in:
parent
aa2f78c404
commit
3465bfdd19
3 changed files with 172 additions and 0 deletions
|
|
@ -4770,6 +4770,26 @@ TRANSLATIONS = {
|
|||
"fr": "d'une personnalisation. Lire avant de retirer.",
|
||||
"en": "of a customization. Read before removing.",
|
||||
},
|
||||
"Installed theme(s) on": {
|
||||
"fr": "Thème(s) installé(s) sur",
|
||||
"en": "Installed theme(s) on",
|
||||
},
|
||||
"A theme carries view copies and SCSS through every": {
|
||||
"fr": "Un thème traîne des copies de vues et des SCSS à travers chaque",
|
||||
"en": "A theme carries view copies and SCSS through every",
|
||||
},
|
||||
"version bump, and a bump can rename what they rely on.": {
|
||||
"fr": "palier de version, et un palier peut renommer ce dont ils dépendent.",
|
||||
"en": "version bump, and a bump can rename what they rely on.",
|
||||
},
|
||||
"Uninstall them properly before migrating?": {
|
||||
"fr": "Les désinstaller proprement avant de migrer ?",
|
||||
"en": "Uninstall them properly before migrating?",
|
||||
},
|
||||
"Kept. Nothing was uninstalled.": {
|
||||
"fr": "Conservés. Rien n'a été désinstallé.",
|
||||
"en": "Kept. Nothing was uninstalled.",
|
||||
},
|
||||
"Nothing to decide yet": {
|
||||
"fr": "Rien à décider pour l'instant",
|
||||
"en": "Nothing to decide yet",
|
||||
|
|
|
|||
|
|
@ -1545,6 +1545,11 @@ class TodoUpgrade:
|
|||
self.dct_progression["state_1_neutralize_database"] = True
|
||||
self.write_config()
|
||||
|
||||
if not self.dct_progression.get("state_1_theme_uninstalled"):
|
||||
self.prompt_uninstall_theme(database_name)
|
||||
self.dct_progression["state_1_theme_uninstalled"] = True
|
||||
self.write_config()
|
||||
|
||||
config_state_1_uninstall_module = self.dct_progression.get(
|
||||
"config_state_1_uninstall_module"
|
||||
)
|
||||
|
|
@ -2706,6 +2711,62 @@ class TodoUpgrade:
|
|||
prefix, sep, label = msg.partition(" - ")
|
||||
print(f"🔷 {prefix}{sep}{t(label)}" if sep else f"🔷 {t(msg)}")
|
||||
|
||||
def installed_theme(self, database_name):
|
||||
"""Thèmes installés, hors theme_default qui EST l'absence de thème."""
|
||||
status, _cmd, output = self.todo_upgrade_execute(
|
||||
f'psql -X -w -d {database_name} -tAc "SELECT name FROM'
|
||||
" ir_module_module WHERE name LIKE 'theme%' AND"
|
||||
" state = 'installed' AND name <> 'theme_default'"
|
||||
' ORDER BY name;"',
|
||||
get_output=True,
|
||||
wait_at_error=False,
|
||||
quiet=True,
|
||||
)
|
||||
if status:
|
||||
return []
|
||||
return [line.strip() for line in (output or []) if line.strip()]
|
||||
|
||||
def prompt_uninstall_theme(self, database_name):
|
||||
"""Proposer de retirer les thèmes AVANT de monter de version.
|
||||
|
||||
Un thème installé traverse la migration : ses copies de vues et ses
|
||||
SCSS suivent chaque palier, et chaque palier peut renommer ce dont
|
||||
ils dépendent. Le retirer d'abord enlève d'un coup une famille
|
||||
entière de pannes, et se refait après.
|
||||
|
||||
« non » par défaut : retirer un thème change l'apparence du site, et
|
||||
ce n'est pas à une migration de le décider à la place de quelqu'un.
|
||||
La question n'est posée que s'il y a un thème à retirer.
|
||||
"""
|
||||
lst_theme = self.installed_theme(database_name)
|
||||
if not lst_theme:
|
||||
return
|
||||
print(
|
||||
f"\n✨ {t('Installed theme(s) on')} '{database_name}' :"
|
||||
f" {', '.join(lst_theme)}"
|
||||
)
|
||||
print(
|
||||
f" {t('A theme carries view copies and SCSS through every')}"
|
||||
f" {t('version bump, and a bump can rename what they rely on.')}"
|
||||
)
|
||||
answer = (
|
||||
self.ask_gate(
|
||||
f"💬 {t('Uninstall them properly before migrating?')}"
|
||||
f" (y/N, {t('(b = go back to a previous step)')}) : "
|
||||
)
|
||||
.strip()
|
||||
.lower()
|
||||
)
|
||||
if answer != "y":
|
||||
print(f"ℹ -> {t('Kept. Nothing was uninstalled.')}")
|
||||
return
|
||||
for theme in lst_theme:
|
||||
self.todo_upgrade_execute(
|
||||
f"./script/addons/uninstall_addons_theme.sh"
|
||||
f" {database_name} {theme}",
|
||||
wait_at_error=False,
|
||||
)
|
||||
|
||||
def show_cow_drift(self, database_name, next_version, mode="diff"):
|
||||
"""Montre les copies COW à risque. Ne touche à rien.
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,97 @@ class TestTheLeftoverReport(unittest.TestCase):
|
|||
self.assertIn("default_transaction_read_only=on", source)
|
||||
|
||||
|
||||
class TestTheMigrationOffersIt(unittest.TestCase):
|
||||
"""La question doit venir AVANT le premier palier, et par défaut non.
|
||||
|
||||
Un thème installé traverse la migration : ses copies de vues et ses SCSS
|
||||
suivent chaque palier, et chaque palier peut renommer ce dont ils
|
||||
dépendent. Le proposer tôt retire d'un coup une famille de pannes.
|
||||
|
||||
Par défaut non : retirer un thème change l'apparence d'un site, et ce
|
||||
n'est pas à une migration de trancher cela à la place de quelqu'un.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
from script.todo import todo_i18n
|
||||
|
||||
self.addCleanup(
|
||||
setattr, todo_i18n, "_current_lang", todo_i18n._current_lang
|
||||
)
|
||||
todo_i18n._current_lang = "en"
|
||||
|
||||
def upgrade(self, lst_theme, answer):
|
||||
from script.todo.todo_upgrade import TodoUpgrade
|
||||
|
||||
obj = TodoUpgrade.__new__(TodoUpgrade)
|
||||
obj.dct_progression = {}
|
||||
obj.lst_command_executed = []
|
||||
obj.write_config = lambda: None
|
||||
obj.installed_theme = lambda db: lst_theme
|
||||
self.lst_cmd = []
|
||||
obj.todo_upgrade_execute = lambda cmd, **kw: (
|
||||
self.lst_cmd.append(cmd),
|
||||
(False, cmd, []),
|
||||
)[1]
|
||||
obj.ask_gate = lambda prompt: answer
|
||||
return obj
|
||||
|
||||
def run_prompt(self, lst_theme, answer):
|
||||
import contextlib
|
||||
import io
|
||||
|
||||
obj = self.upgrade(lst_theme, answer)
|
||||
out = io.StringIO()
|
||||
with contextlib.redirect_stdout(out):
|
||||
obj.prompt_uninstall_theme("db")
|
||||
return self.lst_cmd, out.getvalue()
|
||||
|
||||
def test_the_default_answer_uninstalls_nothing(self):
|
||||
# « Entrée » ne doit RIEN faire : la migration ne décide pas de
|
||||
# l'apparence d'un site à la place de son propriétaire.
|
||||
lst_cmd, text = self.run_prompt(["theme_technolibre"], "")
|
||||
self.assertEqual(lst_cmd, [])
|
||||
self.assertIn("Kept", text)
|
||||
|
||||
def test_no_theme_means_no_question(self):
|
||||
lst_cmd, text = self.run_prompt([], "y")
|
||||
self.assertEqual(lst_cmd, [])
|
||||
self.assertEqual(text, "")
|
||||
|
||||
def test_yes_runs_the_proper_uninstaller(self):
|
||||
lst_cmd, _ = self.run_prompt(["theme_technolibre"], "y")
|
||||
self.assertEqual(len(lst_cmd), 1)
|
||||
self.assertIn(
|
||||
"uninstall_addons_theme.sh db theme_technolibre", lst_cmd[0]
|
||||
)
|
||||
|
||||
def test_every_theme_is_offered_together(self):
|
||||
lst_cmd, _ = self.run_prompt(["theme_a", "theme_b"], "Y")
|
||||
self.assertEqual(len(lst_cmd), 2)
|
||||
|
||||
def test_it_is_asked_before_the_first_bump(self):
|
||||
# L'ordre est le point : posée après le premier palier, la question
|
||||
# arrive quand les copies ont déjà traversé un renommage.
|
||||
import inspect
|
||||
|
||||
from script.todo.todo_upgrade import TodoUpgrade
|
||||
|
||||
source = inspect.getsource(TodoUpgrade.execute_odoo_upgrade)
|
||||
appel = source.index("prompt_uninstall_theme")
|
||||
palier = source.index("4 - Upgrade version with OpenUpgrade")
|
||||
self.assertLess(appel, palier)
|
||||
|
||||
def test_theme_default_is_not_a_theme_to_remove(self):
|
||||
# theme_default EST l'absence de thème : le proposer au retrait
|
||||
# ferait poser une question sans objet à chaque migration.
|
||||
import inspect
|
||||
|
||||
from script.todo.todo_upgrade import TodoUpgrade
|
||||
|
||||
source = inspect.getsource(TodoUpgrade.installed_theme)
|
||||
self.assertIn("name <> 'theme_default'", source)
|
||||
|
||||
|
||||
class TestExitCodes(unittest.TestCase):
|
||||
"""0 rien, 1 des restes, 2 l'outil a échoué — comme les outils voisins."""
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue