From 29894f304b2e2925e21fc5f9ca629b5f84d604f6 Mon Sep 17 00:00:00 2001 From: Christopher Rogos Date: Tue, 7 Jul 2026 09:57:09 +0000 Subject: [PATCH 1/2] [IMP] fs_attachment: two attachments with same name and content --- fs_attachment/models/ir_attachment.py | 13 +++++- fs_attachment/tests/test_fs_attachment.py | 50 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/fs_attachment/models/ir_attachment.py b/fs_attachment/models/ir_attachment.py index bf8cdee3ae..72ca189736 100644 --- a/fs_attachment/models/ir_attachment.py +++ b/fs_attachment/models/ir_attachment.py @@ -472,6 +472,7 @@ def _enforce_meaningful_storage_filename(self) -> None: Keeping the same meaning and mimetype is important to also ease to provide a meaningful and SEO friendly URL to the file in the filesystem storage. """ + renamed_attachments = {} for attachment in self: if not self._is_file_from_a_storage(attachment.store_fname): continue @@ -485,7 +486,17 @@ def _enforce_meaningful_storage_filename(self) -> None: new_filename_with_path = os.path.join( os.path.dirname(filename), new_filename ) - fs.rename(filename, new_filename_with_path) + + if filename in renamed_attachments: + if renamed_attachments[filename] == new_filename_with_path: + # we already renamed this file, no need to rename it again + continue + else: + fs.copy(renamed_attachments[filename], new_filename_with_path) + else: + fs.rename(filename, new_filename_with_path) + renamed_attachments[filename] = new_filename_with_path + attachment.fs_filename = new_filename # we need to update the store_fname with the new filename by # calling the write method of the field since the write method diff --git a/fs_attachment/tests/test_fs_attachment.py b/fs_attachment/tests/test_fs_attachment.py index f52c3ec900..6080fc2d5b 100644 --- a/fs_attachment/tests/test_fs_attachment.py +++ b/fs_attachment/tests/test_fs_attachment.py @@ -431,6 +431,56 @@ def test_create_attachments_basic_user(self): } ) + def test_create_two_attachments(self): + self.temp_backend.use_as_default_for_attachments = True + res = self.ir_attachment_model.create( + [ + {"name": "test2.txt", "raw": b"content"}, + {"name": "test.txt", "raw": b"content"}, + ] + ) + self.assertEqual(len(res), 2) + + def test_create_two_attachments_same_name(self): + self.temp_backend.use_as_default_for_attachments = True + res = self.ir_attachment_model.create( + [ + {"name": "test.txt", "raw": b"content"}, + {"name": "test.txt", "raw": b"content"}, + ] + ) + self.assertEqual(len(res), 2) + attachment1 = res[0] + attachment2 = res[1] + self.assertEqual(attachment1.name, "test.txt") + self.assertEqual(attachment2.name, "test.txt") + self.assertNotEqual(attachment1.store_fname, attachment2.store_fname) + self.assertEqual(attachment1.raw, attachment2.raw) + + attachment1.raw = b"new content" + self.assertNotEqual(attachment1.raw, attachment2.raw) + + attachment2 = self.ir_attachment_model.browse(attachment2.id) + self.assertEqual(attachment2.raw, b"content") + + def test_create_two_attachments_differnt_call(self): + self.temp_backend.use_as_default_for_attachments = True + res = self.ir_attachment_model.create( + [ + {"name": "test.txt", "raw": b"content"}, + ] + ) + res2 = self.ir_attachment_model.create( + [ + {"name": "test.txt", "raw": b"content"}, + ] + ) + self.assertEqual(len(res), 1) + self.assertEqual(len(res2), 1) + + self.assertNotEqual(res[0].store_fname, res2[0].store_fname) + self.assertEqual(res[0].raw, res2[0].raw) + def test_update_png_to_svg(self): b64_data_png = ( b"iVBORw0KGgoAAAANSUhEUgAAADMAAAAhCAIAAAD73QTtAAAAA3NCSVQICAjb4U/gAA" From 1c43e2b2509ef5f6dbc32f434c8d2b8e37f9f5d1 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 3 Aug 2026 08:17:09 +0000 Subject: [PATCH 2/2] [BOT] post-merge updates --- README.md | 2 +- fs_attachment/README.rst | 2 +- fs_attachment/__manifest__.py | 2 +- fs_attachment/static/description/index.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 12221a1d6c..036764cf9c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Available addons ---------------- addon | version | maintainers | summary --- | --- | --- | --- -[fs_attachment](fs_attachment/) | 18.0.2.2.2 | lmignon | Store attachments on external object store +[fs_attachment](fs_attachment/) | 18.0.2.2.3 | lmignon | Store attachments on external object store [fs_attachment_s3](fs_attachment_s3/) | 18.0.1.2.1 | lmignon | Store attachments into S3 complient filesystem [fs_base_multi_image](fs_base_multi_image/) | 18.0.1.0.0 | lmignon | Mulitple Images from External File System [fs_file](fs_file/) | 18.0.1.0.0 | lmignon | Field to store files into filesystem storages diff --git a/fs_attachment/README.rst b/fs_attachment/README.rst index c13037b9d3..b50b5d9d27 100644 --- a/fs_attachment/README.rst +++ b/fs_attachment/README.rst @@ -11,7 +11,7 @@ Base Attachment Object Store !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:2247c3bace615168d55c67d0dffed04dda62c77a89434b15d04934e8a1f78184 + !! source digest: sha256:6265c71ea4329d84a2b87af2198802b52d2e3150eafee20bf63d747c8010f004 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/fs_attachment/__manifest__.py b/fs_attachment/__manifest__.py index 7ce2868501..c6c1ba9413 100644 --- a/fs_attachment/__manifest__.py +++ b/fs_attachment/__manifest__.py @@ -5,7 +5,7 @@ { "name": "Base Attachment Object Store", "summary": "Store attachments on external object store", - "version": "18.0.2.2.2", + "version": "18.0.2.2.3", "author": "Camptocamp, ACSONE SA/NV, Odoo Community Association (OCA)", "license": "AGPL-3", "development_status": "Beta", diff --git a/fs_attachment/static/description/index.html b/fs_attachment/static/description/index.html index 21d18e4314..f3194d3593 100644 --- a/fs_attachment/static/description/index.html +++ b/fs_attachment/static/description/index.html @@ -372,7 +372,7 @@

Base Attachment Object Store

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:2247c3bace615168d55c67d0dffed04dda62c77a89434b15d04934e8a1f78184 +!! source digest: sha256:6265c71ea4329d84a2b87af2198802b52d2e3150eafee20bf63d747c8010f004 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/storage Translate me on Weblate Try me on Runboat

In some cases, you need to store attachment in another system that the