Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Available addons
----------------
addon | version | maintainers | summary
--- | --- | --- | ---
[fs_attachment](fs_attachment/) | 19.0.1.1.1 | <a href='https://github.com/lmignon'><img src='https://github.com/lmignon.png' width='32' height='32' style='border-radius:50%;' alt='lmignon'/></a> | Store attachments on external object store
[fs_attachment](fs_attachment/) | 19.0.1.1.2 | <a href='https://github.com/lmignon'><img src='https://github.com/lmignon.png' width='32' height='32' style='border-radius:50%;' alt='lmignon'/></a> | Store attachments on external object store
[fs_attachment_s3](fs_attachment_s3/) | 19.0.1.2.0 | <a href='https://github.com/lmignon'><img src='https://github.com/lmignon.png' width='32' height='32' style='border-radius:50%;' alt='lmignon'/></a> | Store attachments into S3 complient filesystem
[fs_storage](fs_storage/) | 19.0.1.1.2 | | Implement the concept of Storage with amazon S3, sftp...

Expand Down
2 changes: 1 addition & 1 deletion fs_attachment/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Base Attachment Object Store
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e8ac601a7fe9a3171bd2b5af73ca368dba643c04e2eb88ef05862c3ad670f07a
!! source digest: sha256:e7df98b1892bad6c20bbc89c6e1abd2908cbeecd561b584868b6ed6109190326
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
Expand Down
2 changes: 1 addition & 1 deletion fs_attachment/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{
"name": "Base Attachment Object Store",
"summary": "Store attachments on external object store",
"version": "19.0.1.1.1",
"version": "19.0.1.1.2",
"author": "Camptocamp, ACSONE SA/NV, Odoo Community Association (OCA)",
"license": "AGPL-3",
"development_status": "Beta",
Expand Down
25 changes: 24 additions & 1 deletion fs_attachment/models/ir_attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,18 @@ def _storage(self):
storage = super()._storage()
return storage

@api.depends("store_fname", "db_datas")
def _compute_raw(self):
"""Always expose raw payload as bytes.

Some callers (e.g. account EDI helpers) slice the value returned by
``raw`` and crash when it is ``False`` for 0-byte attachments.
"""
res = super()._compute_raw()
false_attachments = self.filtered(lambda att: not att.raw)
false_attachments.raw = b""
return res

@api.model_create_multi
def create(self, vals_list):
"""
Expand Down Expand Up @@ -467,6 +479,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
Expand All @@ -480,7 +493,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
Expand Down
2 changes: 1 addition & 1 deletion fs_attachment/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ <h1>Base Attachment Object Store</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e8ac601a7fe9a3171bd2b5af73ca368dba643c04e2eb88ef05862c3ad670f07a
!! source digest: sha256:e7df98b1892bad6c20bbc89c6e1abd2908cbeecd561b584868b6ed6109190326
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/storage/tree/19.0/fs_attachment"><img alt="OCA/storage" src="https://img.shields.io/badge/github-OCA%2Fstorage-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/storage-19-0/storage-19-0-fs_attachment"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/storage&amp;target_branch=19.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>In some cases, you need to store attachment in another system that the
Expand Down
56 changes: 56 additions & 0 deletions fs_attachment/tests/test_fs_attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ def test_create_attachment_with_meaningful_name(self):
with attachment.open("rb") as f:
self.assertEqual(f.read(), new_content)

def test_create_attachment_with_no_payload_has_bytes_raw(self):
attachment = self.ir_attachment_model.create({"name": "empty.txt"})

self.assertEqual(attachment.raw, b"")
self.assertEqual(attachment.file_size, 0)

def test_open_attachment_in_db(self):
self.env["ir.config_parameter"].sudo().set_param("ir_attachment.location", "db")
content = b"This is a test attachment in db"
Expand Down Expand Up @@ -446,6 +452,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"
Expand Down