From 6aed745a480a31ee577c63aa7eb1ca88a7399a0d Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 2 Oct 2026 13:23:40 +0200 Subject: [PATCH 1/4] [IMP] storage_file: show archived files Gray out the archived files in the list, flag them with a ribbon on the form, and add a filter to list them. --- storage_file/views/storage_file_view.xml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/storage_file/views/storage_file_view.xml b/storage_file/views/storage_file_view.xml index acc313b06f..7d4a9fa929 100644 --- a/storage_file/views/storage_file_view.xml +++ b/storage_file/views/storage_file_view.xml @@ -3,7 +3,8 @@ storage.file - + + @@ -17,6 +18,13 @@ storage.file
+ +
+ Archived From 1c2a53836e9453e4d8bdd72e45470cb74b556c74 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 1 Oct 2026 17:24:33 +0200 Subject: [PATCH 3/4] [FIX] storage_image_product: show relations of archived images List the relations of archived images on products and variants, grayed out, and skip them when computing the main image. --- .../models/product_product.py | 15 ++++++----- .../models/product_template.py | 10 ++++++-- .../tests/test_product_image_relation.py | 25 +++++++++++++++++++ storage_image_product/views/storage_image.xml | 16 ++++++------ 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/storage_image_product/models/product_product.py b/storage_image_product/models/product_product.py index cb73c4486d..ae0a8973ae 100644 --- a/storage_image_product/models/product_product.py +++ b/storage_image_product/models/product_product.py @@ -48,7 +48,7 @@ def _compute_variant_image_ids(self): img_relations.add(image_rel.id) variant.variant_image_ids = list(img_relations) if img_relations else False - @api.depends("variant_image_ids.sequence") + @api.depends("variant_image_ids.sequence", "variant_image_ids.image_active") def _compute_main_image_id(self): for record in self: record.main_image_id = record._get_main_image() @@ -59,12 +59,15 @@ def _select_main_image(self, images): ).image_id def _get_main_image(self): - match_image = self.variant_image_ids.filtered( - lambda i: i.attribute_value_ids - == self.mapped( - "product_template_attribute_value_ids.product_attribute_value_id" + images = self.variant_image_ids.filtered("image_active") + match_image = images.filtered( + lambda i: ( + i.attribute_value_ids + == self.mapped( + "product_template_attribute_value_ids.product_attribute_value_id" + ) ) ) if match_image: return self._select_main_image(match_image) - return self._select_main_image(self.variant_image_ids) + return self._select_main_image(images) diff --git a/storage_image_product/models/product_template.py b/storage_image_product/models/product_template.py index 705919cd3e..71d0d3bb2f 100644 --- a/storage_image_product/models/product_template.py +++ b/storage_image_product/models/product_template.py @@ -34,12 +34,18 @@ class ProductTemplate(models.Model): string="Main medium image URL", related="main_image_id.image_medium_url" ) - @api.depends("image_ids", "image_ids.sequence", "image_ids.image_id") + @api.depends( + "image_ids", + "image_ids.sequence", + "image_ids.image_id", + "image_ids.image_active", + ) def _compute_main_image_id(self): for record in self: record.main_image_id = record._get_main_image() def _get_main_image(self): + images = self.image_ids.filtered("image_active") return fields.first( - self.image_ids.sorted(key=lambda i: (i.sequence, i.image_id)) + images.sorted(key=lambda i: (i.sequence, i.image_id)) ).image_id diff --git a/storage_image_product/tests/test_product_image_relation.py b/storage_image_product/tests/test_product_image_relation.py index c7e33e5143..09e6f47801 100644 --- a/storage_image_product/tests/test_product_image_relation.py +++ b/storage_image_product/tests/test_product_image_relation.py @@ -202,3 +202,28 @@ def test_drop_template_attribute_value_propagation_to_image(self): self.env.ref("product.product_attribute_value_1") not in black_image.attribute_value_ids ) + + def test_archived_image(self): + logo_relation, white_relation = self.env["product.image.relation"].create( + [ + { + "product_tmpl_id": self.template.id, + "image_id": self.logo_image.id, + "sequence": 1, + }, + { + "product_tmpl_id": self.template.id, + "image_id": self.white_image.id, + "sequence": 2, + }, + ] + ) + self.assertEqual(self.template.main_image_id, self.logo_image) + self.assertEqual(self.product_a.main_image_id, self.logo_image) + self.logo_image.active = False + # Still listed, but not the main image anymore + self.assertEqual(self.template.image_ids, logo_relation | white_relation) + self.assertIn(logo_relation, self.product_a.variant_image_ids) + self.assertFalse(logo_relation.image_active) + self.assertEqual(self.template.main_image_id, self.white_image) + self.assertEqual(self.product_a.main_image_id, self.white_image) diff --git a/storage_image_product/views/storage_image.xml b/storage_image_product/views/storage_image.xml index c3a4cd140f..1c385fd2a5 100644 --- a/storage_image_product/views/storage_image.xml +++ b/storage_image_product/views/storage_image.xml @@ -8,16 +8,16 @@ - + @@ -34,14 +34,14 @@ - + From 4817a15f9031093b09a3803f8a9973f1e13bfbeb Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 1 Oct 2026 17:24:43 +0200 Subject: [PATCH 4/4] [FIX] storage_media_product: show relations of archived media Rename the 'active' field of the relations to 'media_active': named 'active', it hides the relations of archived media everywhere (eg: on the product and variant forms), while they must show up grayed out. --- storage_media_product/models/product.py | 5 ++- storage_media_product/tests/__init__.py | 1 + .../tests/test_product_media.py | 36 +++++++++++++++++++ storage_media_product/views/product.xml | 8 ++--- 4 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 storage_media_product/tests/__init__.py create mode 100644 storage_media_product/tests/test_product_media.py diff --git a/storage_media_product/models/product.py b/storage_media_product/models/product.py index a885f7f80d..b1365da0c1 100644 --- a/storage_media_product/models/product.py +++ b/storage_media_product/models/product.py @@ -66,7 +66,10 @@ class ProductMediaRelation(models.Model): url_path = fields.Char(related="media_id.url_path", readonly=True) media_type_id = fields.Many2one(related="media_id.media_type_id", readonly=True) is_public = fields.Boolean(related="media_id.file_id.is_public", readonly=True) - active = fields.Boolean(related="media_id.active", readonly=True) + # Gray out the relations of archived media. + # NOTE: do not call this field `active`, it would hide the relations + # (eg: on the product) and we want to display them anyway. + media_active = fields.Boolean(related="media_id.active", readonly=True) @api.depends("media_id", "product_tmpl_id.attribute_line_ids.value_ids") def _compute_available_attribute(self): diff --git a/storage_media_product/tests/__init__.py b/storage_media_product/tests/__init__.py new file mode 100644 index 0000000000..5c2a90e059 --- /dev/null +++ b/storage_media_product/tests/__init__.py @@ -0,0 +1 @@ +from . import test_product_media diff --git a/storage_media_product/tests/test_product_media.py b/storage_media_product/tests/test_product_media.py new file mode 100644 index 0000000000..b5c0ae9743 --- /dev/null +++ b/storage_media_product/tests/test_product_media.py @@ -0,0 +1,36 @@ +# Copyright 2026 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import base64 + +from odoo.tests import Form + +from odoo.addons.component.tests.common import TransactionComponentCase + + +class TestProductMedia(TransactionComponentCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.media = cls.env["storage.media"].create( + {"name": "doc.txt", "data": base64.b64encode(b"doc")} + ) + cls.product = cls.env["product.product"].create({"name": "Media product"}) + cls.relation = cls.env["product.media.relation"].create( + { + "media_id": cls.media.id, + "product_tmpl_id": cls.product.product_tmpl_id.id, + } + ) + + def test_archived_relation_listed_on_template(self): + product_tmpl = self.product.product_tmpl_id + view = "storage_media_product.product_template_only_form_view" + self.media.active = False + self.assertFalse(self.relation.media_active) + self.assertEqual(len(Form(product_tmpl, view=view).media_ids), 1) + + def test_archived_relation_listed_on_variant(self): + view = "storage_media_product.product_normal_form_view" + self.media.active = False + self.assertEqual(len(Form(self.product, view=view).variant_media_ids), 1) diff --git a/storage_media_product/views/product.xml b/storage_media_product/views/product.xml index d03831bbfa..9a29a9a067 100644 --- a/storage_media_product/views/product.xml +++ b/storage_media_product/views/product.xml @@ -36,14 +36,14 @@ product.media.relation - +