Skip to content

[18.0][IMP] storage_file: improve public/private behavior#623

Open
simahawk wants to merge 9 commits into
OCA:18.0from
camptocamp:18-public-private-imp
Open

[18.0][IMP] storage_file: improve public/private behavior#623
simahawk wants to merge 9 commits into
OCA:18.0from
camptocamp:18-public-private-imp

Conversation

@simahawk
Copy link
Copy Markdown
Contributor

Improve handling of storage backend visibility and allow serving private files directly through Odoo.

Changes summary:

storage_file

  • Serve private files stored on external backends (S3, etc.) directly via the Odoo controller, removing the need for a private CDN with complex authentication. When an external backend has no base_url, the file URL falls back to the /storage.file/<slug> route.
  • Use record rules to control file access: public users can only read files on public backends; internal users can read all files.
  • Make backend_id editable on forms with a pre-loaded default (configurable via ir.config_parameter).
  • Make is_public visible on the backend form regardless of served_by type.
  • Add search filter for public/private files.

storage_image / storage_media

  • Add search filter for public/private.
  • Make backend_id visible (readonly) on forms with a configurable default.

storage_media_product:

  • Show is_public on the product media view

NOTE: as the backend is now editable, the best is to use #622 to take care of swapping backends on change.

@simahawk simahawk force-pushed the 18-public-private-imp branch from eaccbba to bae2003 Compare May 28, 2026 10:57
@OCA-git-bot OCA-git-bot added series:18.0 mod:storage_media Module storage_media mod:storage_file Module storage_file mod:storage_image Module storage_image mod:storage_media_product Module storage_media_product labels May 28, 2026
simahawk added 9 commits May 28, 2026 13:46
For the case of private files stored externally it might be necessary
to view the file via Odoo directly because you can avoid to setup a private CDN
with complex authentication (eg: token, vpn, etc).

In this way, the files can be proxied by the controller.
So far the backend was added only auto-magically on media or image creation.
However, this is a partial behavior and since the backend is not editable
you cannot decide where to put your file if not via global conf.

This chance allows to edit the backend while still pre-loading the default one.
Provide data so that the URL is generated properly.
@simahawk simahawk force-pushed the 18-public-private-imp branch from bae2003 to 40b685a Compare May 28, 2026 11:47
@OCA-git-bot OCA-git-bot added the mod:storage_thumbnail Module storage_thumbnail label May 28, 2026
Copy link
Copy Markdown
Contributor

@SilvioC2C SilvioC2C left a comment

Choose a reason for hiding this comment

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

Overall LGTM, a few things to check imho

def test_reflects_backend_flag(self):
self.assertFalse(self.storage_file.is_public)
self.backend.is_public = True
self.storage_file.invalidate_recordset(["is_public"])
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.

This cache invalidation should not be necessary. Does this test still succeed if this line is removed?

result = self.env["storage.file"].search(
[("is_public", "=", True), ("id", "=", self.storage_file.id)]
)
self.assertIn(self.storage_file, result)
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.

Nitpicking:

Suggested change
self.assertIn(self.storage_file, result)
self.assertEqual(self.storage_file, result)

result = self.env["storage.file"].search(
[("is_public", "=", False), ("id", "=", self.storage_file.id)]
)
self.assertIn(self.storage_file, result)
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.

Nitpicking:

Suggested change
self.assertIn(self.storage_file, result)
self.assertEqual(self.storage_file, result)

def test_is_public_reflects_backend(self):
self.assertFalse(self.storage_image.is_public)
self.backend.sudo().is_public = True
self.storage_image.invalidate_recordset(["is_public"])
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.

Same as above

Comment on lines +14 to +18
def setUp(self):
super().setUp()
self.media = self.env["storage.media"].create(
{"name": "test-media.txt", "backend_id": self.backend.id}
)
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.

Can't this be done in setUpClass()?


def test_is_public_reflects_backend(self):
self.backend.sudo().is_public = False
self.media.invalidate_recordset(["is_public"])
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.

Same as above

self.media.invalidate_recordset(["is_public"])
self.assertFalse(self.media.is_public)
self.backend.sudo().is_public = True
self.media.invalidate_recordset(["is_public"])
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.

Same as above

Copy link
Copy Markdown
Contributor

@hparfr hparfr left a comment

Choose a reason for hiding this comment

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

Interesting, two minor remarks

try:
storage_file.check_access("read")
except AccessError as err:
raise NotFound() from err
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.

why hidding AccessError ?

def _get_url_for_file(self, storage_file, exclude_base_url=False):
"""Return final full URL for given file."""
backend = self.sudo()
if backend.served_by == "odoo":
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.

backend.servedy_by is external but the file is served by odoo

It's confusing

May be add a new served_by value like "force_odoo_for_external_backend" or whatever :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved mod:storage_file Module storage_file mod:storage_image Module storage_image mod:storage_media_product Module storage_media_product mod:storage_media Module storage_media mod:storage_thumbnail Module storage_thumbnail series:18.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants