From e75ab8b1287fe924bfa80b8568f1b1ddfc525463 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 17 Aug 2026 03:17:18 -0400 Subject: [PATCH] [FIX] addons: browse the leftovers by integer, not by the string psql gave MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deleting the leftovers failed on « the database search does not have the ids (('4457',)) and has extra ids ((4457,)) ». psql returns text; the ids went into browse() as strings, Odoo compared them against integers, found nothing and refused the whole batch. Nothing was lost: the backup runs before the deletion, and the tool said plainly that nothing was removed. Measured after the fact — 15 files saved, 15 attachments still in database. --- FR --- [FIX] addons : parcourir les restes par entier, pas par la chaîne de psql L'effacement échouait sur « la recherche en base n'a pas les identifiants (('4457',)) et a des identifiants supplémentaires ((4457,)) ». psql rend du texte ; les identifiants partaient dans browse() en chaînes, Odoo les comparait à des entiers, ne trouvait rien et refusait tout le lot. Rien n'a été perdu : la sauvegarde précède l'effacement, et l'outil a dit franchement que rien n'avait été retiré. Vérifié après coup — 15 fichiers sauvegardés, 15 pièces jointes toujours en base. Assisted-by: Claude Opus 5 --- script/addons/theme_leftover.py | 6 ++++- test/test_uninstall_addons_theme.py | 38 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/script/addons/theme_leftover.py b/script/addons/theme_leftover.py index 9e2951b..ec8cd80 100755 --- a/script/addons/theme_leftover.py +++ b/script/addons/theme_leftover.py @@ -181,7 +181,11 @@ def delete_attachments(database, lst_row, config_path="./config.conf"): Un DELETE en SQL laisserait les fichiers orphelins et les caches incohérents ; unlink() fait le ménage complet, dans toutes les versions. """ - lst_id = [row.split("|")[0] for row in lst_row] + # int(), pas la chaîne du psql : browse(['4457']) fait échouer Odoo sur + # « n'a pas les identifiants (('4457',)) et a des identifiants + # supplémentaires ((4457,)) » — il compare des chaînes à des entiers. + # Mesuré sur une vraie base, l'effacement n'a rien retiré. + lst_id = [int(row.split("|")[0]) for row in lst_row] script = ( f"env['ir.attachment'].browse({lst_id!r}).unlink()\n" "env.cr.commit()\n" diff --git a/test/test_uninstall_addons_theme.py b/test/test_uninstall_addons_theme.py index 38b3298..1b17920 100755 --- a/test/test_uninstall_addons_theme.py +++ b/test/test_uninstall_addons_theme.py @@ -376,6 +376,44 @@ class TestTheQuestionMustBeVisible(unittest.TestCase): self.assertIn("sys.stdout.isatty()", source) +class TestTheIdentifiersSentToOdoo(unittest.TestCase): + """browse() veut des ENTIERS ; psql rend des chaînes. + + Mesuré sur une vraie base : browse(['4457']) fait échouer Odoo sur + « la recherche en base n'a pas les identifiants (('4457',)) et a des + identifiants supplémentaires ((4457,)) ». Il compare des chaînes à des + entiers, ne retrouve rien, et refuse. L'effacement n'a rien retiré — + heureusement, la sauvegarde était déjà faite. + """ + + def test_the_script_browses_integers(self): + import inspect + + source = inspect.getsource(theme_leftover.delete_attachments) + self.assertIn("int(row.split", source) + + def test_the_pushed_script_carries_no_quoted_id(self): + # Le rendu exact de ce qui part dans le shell : une seule apostrophe + # autour d'un identifiant et Odoo refuse tout le lot. + pushed = {} + original = theme_leftover.subprocess.run + theme_leftover.subprocess.run = ( + lambda *a, **kw: pushed.update(script=kw.get("input", "")) + or type("R", (), {"returncode": 0, "stdout": "", "stderr": ""})() + ) + self.addCleanup(setattr, theme_leftover.subprocess, "run", original) + theme_leftover.delete_attachments( + "db", ["4457|/theme_x/a.scss|2021-03-04", "4768|/b.css|d"] + ) + self.assertIn("browse([4457, 4768])", pushed["script"]) + self.assertNotIn("'4457'", pushed["script"]) + + def test_a_non_numeric_row_is_not_sent_silently(self): + # Mieux vaut échouer ici que pousser un script qu'Odoo refusera. + with self.assertRaises(ValueError): + theme_leftover.delete_attachments("db", ["zz|/a|d"]) + + class TestTheMisleadingErrorCode(unittest.TestCase): """« 1 » veut dire « il reste des choses », pas « ça a raté »."""