Skip to content
Open
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 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": "16.0.2.0.1",
"version": "16.0.2.0.4",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version should not be updated manually.

"author": "Camptocamp, ACSONE SA/NV, Odoo Community Association (OCA)",
"license": "AGPL-3",
"development_status": "Beta",
Expand Down
22 changes: 22 additions & 0 deletions fs_attachment/models/ir_attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,29 @@ def write(self, vals):
).write(vals)

if "name" in vals:
# Renaming requires rebuilding the storage filename so URLs
# stay meaningful — apply to every record in the recordset.
self._enforce_meaningful_storage_filename()
elif "datas" in vals or "raw" in vals:
# Content was rewritten without touching the name.
# ``_storage_file_write`` just replaced ``store_fname`` but
# the sibling ``fs_filename`` / ``fs_storage_*`` fields are
# NOT recomputed. A stale ``NULL`` ``fs_filename`` defeats
# the gate in ``IrBinary._get_fs_attachment_for_field``, so
# base Odoo falls through to ``Stream.from_attachment``,
# which passes the raw ``store_fname``
# (e.g. ``"azure_attachments://<hash>"``) straight to
# ``os.stat`` and raises ``FileNotFoundError`` → 500 on
# every ``/web/image/...`` call.
# Only reconcile rows actually in the broken shape: running
# ``_enforce_meaningful_storage_filename`` on a record that
# already has an ``fs_filename`` would trigger a spurious
# ``fs.rename()`` round-trip against the storage.
broken = self.filtered(
lambda a: a.store_fname and not a.fs_filename
)
if broken:
broken._enforce_meaningful_storage_filename()

return True

Expand Down
20 changes: 20 additions & 0 deletions fs_attachment/readme/newsfragments/write_guard_datas.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Reconcile ``fs_filename`` when ``datas`` / ``raw`` is rewritten.

``ir.attachment.write`` only called ``_enforce_meaningful_storage_filename``
when ``name`` was in ``vals``. Every code path that updates an attachment's
content without touching its name — asset-bundle regeneration, ORM image
field updates, inline kanban image writes, the ``_force_storage``
migration itself — rewrote ``store_fname`` via ``_storage_file_write``
but left ``fs_filename`` / ``fs_storage_code`` / ``fs_storage_id``
stale. A ``NULL`` ``fs_filename`` defeats the gate in
``IrBinary._get_fs_attachment_for_field``, so base Odoo falls through
to ``Stream.from_attachment``, which passes the raw ``store_fname``
(e.g. ``"azure_attachments://<hash>"``) straight to ``os.stat`` and
raises ``FileNotFoundError`` — observed in production as 500s on every
``/web/image/...`` request for that record.

Extending the guard to fire on ``"datas"`` / ``"raw"`` keeps
``fs_filename`` consistent with ``store_fname`` after every content
rewrite. ``_enforce_meaningful_storage_filename`` is idempotent and
already no-ops when nothing needs to change, so the widened guard has
no downside for the existing ``"name"`` path.
Loading