diff --git a/README.md b/README.md
index 28b94bf101..ab1b5426e3 100644
--- a/README.md
+++ b/README.md
@@ -38,13 +38,13 @@ addon | version | maintainers | summary
[storage_backend_ftp](storage_backend_ftp/) | 18.0.1.0.0 | | Implement FTP Storage
[storage_backend_s3](storage_backend_s3/) | 18.0.1.1.0 | | Implement amazon S3 Storage
[storage_backend_sftp](storage_backend_sftp/) | 18.0.1.0.0 | | Implement SFTP Storage
-[storage_file](storage_file/) | 18.0.1.1.0 | | Storage file in storage backend
+[storage_file](storage_file/) | 18.0.1.2.0 | | Storage file in storage backend
[storage_file_swap_backend_queue](storage_file_swap_backend_queue/) | 18.0.1.1.0 |
| Delegate storage file backend swap to queue jobs
-[storage_image](storage_image/) | 18.0.1.1.0 | | Store image and resized image in a storage backend
-[storage_image_product](storage_image_product/) | 18.0.1.0.2 | | Link images to products and categories
-[storage_media](storage_media/) | 18.0.1.2.0 | | Give the posibility to store media data in Odoo
-[storage_media_product](storage_media_product/) | 18.0.1.0.1 | | Link media to products and categories
-[storage_thumbnail](storage_thumbnail/) | 18.0.1.0.0 | | Abstract module that add the possibility to have thumbnail
+[storage_image](storage_image/) | 18.0.1.2.0 | | Store image and resized image in a storage backend
+[storage_image_product](storage_image_product/) | 18.0.1.1.0 | | Link images to products and categories
+[storage_media](storage_media/) | 18.0.1.3.0 | | Give the posibility to store media data in Odoo
+[storage_media_product](storage_media_product/) | 18.0.1.1.0 | | Link media to products and categories
+[storage_thumbnail](storage_thumbnail/) | 18.0.1.1.0 | | Abstract module that add the possibility to have thumbnail
[//]: # (end addons)
diff --git a/checklog-odoo.cfg b/checklog-odoo.cfg
index 0b55b7bf66..ce6fa8d678 100644
--- a/checklog-odoo.cfg
+++ b/checklog-odoo.cfg
@@ -1,3 +1,4 @@
[checklog-odoo]
ignore=
WARNING.* 0 failed, 0 error\(s\).*
+ WARNING.*DeprecationWarning: PyUnicode_FromUnicode\(NULL, size\) is deprecated; use PyUnicode_New\(\) instead
diff --git a/storage_file/README.rst b/storage_file/README.rst
index d17d2cc992..eb7a8e63af 100644
--- a/storage_file/README.rst
+++ b/storage_file/README.rst
@@ -11,7 +11,7 @@ Storage File
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:14eff9ac3e34f90b55e98e5f1d8e938390db9e5d7c2f421d370143c3a26f5e1b
+ !! source digest: sha256:c7ccd99c8428f973c93fbbdde0021b61a5db3077bc2d7722a2f929611f8ac415
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
diff --git a/storage_file/__manifest__.py b/storage_file/__manifest__.py
index 91abd363a5..87c22813d0 100644
--- a/storage_file/__manifest__.py
+++ b/storage_file/__manifest__.py
@@ -5,7 +5,7 @@
{
"name": "Storage File",
"summary": "Storage file in storage backend",
- "version": "18.0.1.1.0",
+ "version": "18.0.1.2.0",
"category": "Storage",
"website": "https://github.com/OCA/storage",
"author": " Akretion, Odoo Community Association (OCA)",
@@ -23,5 +23,6 @@
"data/ir_cron.xml",
"data/storage_backend.xml",
"wizards/swap_backend.xml",
+ "data/ir_config_parameter.xml",
],
}
diff --git a/storage_file/controllers/main.py b/storage_file/controllers/main.py
index c2b29b44da..365b6b4682 100644
--- a/storage_file/controllers/main.py
+++ b/storage_file/controllers/main.py
@@ -1,7 +1,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from werkzeug.exceptions import NotFound
from odoo import http
+from odoo.exceptions import AccessError
from odoo.http import request
@@ -13,6 +15,15 @@ def content_common(self, slug_name_with_id, token=None, download=None, **kw):
storage_file = request.env["storage.file"].get_from_slug_name_with_id(
slug_name_with_id
)
+ if not storage_file.exists():
+ raise NotFound()
+ try:
+ storage_file.check_access("read")
+ except AccessError as err:
+ # If you don't have access you should not know
+ # that the file exists (as anon user).
+ # You can inspect the traceback to see it's coming from an access error.
+ raise NotFound() from err
stream = request.env["ir.binary"]._get_stream_from(
storage_file, field_name="data"
)
diff --git a/storage_file/data/ir_config_parameter.xml b/storage_file/data/ir_config_parameter.xml
new file mode 100644
index 0000000000..68dafd61d1
--- /dev/null
+++ b/storage_file/data/ir_config_parameter.xml
@@ -0,0 +1,7 @@
+
+
+
+ storage.file.backend_id
+
+
+
diff --git a/storage_file/i18n/it.po b/storage_file/i18n/it.po
index 31b90b9997..fc633a1c24 100644
--- a/storage_file/i18n/it.po
+++ b/storage_file/i18n/it.po
@@ -16,6 +16,17 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.6.2\n"
+#. module: storage_file
+#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
+msgid ""
+"Note: Without a CDN base URL, files will be proxied\n"
+" through Odoo. This is acceptable for internal use with "
+"low traffic\n"
+" but suboptimal for high-volume public access as every "
+"request loads\n"
+" the file from the storage backend into memory."
+msgstr ""
+
#. module: storage_file
#. odoo-python
#: code:addons/storage_file/models/storage_file.py:0
@@ -40,6 +51,11 @@ msgid ""
"%s"
msgstr ""
+#. module: storage_file
+#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
+msgid "Backend"
+msgstr ""
+
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__backend_view_use_internal_url
msgid "Backend View Use Internal Url"
@@ -206,6 +222,11 @@ msgstr ""
msgid "Files to swap"
msgstr ""
+#. module: storage_file
+#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
+msgid "Group By"
+msgstr ""
+
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__internal_url
msgid "HTTP URL to load the file directly from storage."
@@ -244,6 +265,7 @@ msgstr "URL interno"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__is_public
+#: model:ir.model.fields,field_description:storage_file.field_storage_file__is_public
msgid "Is Public"
msgstr "È pubblico"
@@ -302,11 +324,26 @@ msgstr ""
msgid "Please select at least one file."
msgstr ""
+#. module: storage_file
+#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
+msgid "Private"
+msgstr ""
+
+#. module: storage_file
+#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
+msgid "Public"
+msgstr ""
+
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid "Recompute base URL for files"
msgstr "Ricalcola l'URL base per i file"
+#. module: storage_file
+#: model:ir.model.fields,help:storage_file.field_storage_file__is_public
+msgid "Reflects the `is_public` flag of the related backend."
+msgstr ""
+
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__relative_path
msgid "Relative Path"
diff --git a/storage_file/i18n/storage_file.pot b/storage_file/i18n/storage_file.pot
index 4274550bc2..5078abea17 100644
--- a/storage_file/i18n/storage_file.pot
+++ b/storage_file/i18n/storage_file.pot
@@ -13,6 +13,15 @@ msgstr ""
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
+#. module: storage_file
+#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
+msgid ""
+"Note: Without a CDN base URL, files will be proxied\n"
+" through Odoo. This is acceptable for internal use with low traffic\n"
+" but suboptimal for high-volume public access as every request loads\n"
+" the file from the storage backend into memory."
+msgstr ""
+
#. module: storage_file
#. odoo-python
#: code:addons/storage_file/models/storage_file.py:0
@@ -37,6 +46,11 @@ msgid ""
"%s"
msgstr ""
+#. module: storage_file
+#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
+msgid "Backend"
+msgstr ""
+
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__backend_view_use_internal_url
msgid "Backend View Use Internal Url"
@@ -192,6 +206,11 @@ msgstr ""
msgid "Files to swap"
msgstr ""
+#. module: storage_file
+#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
+msgid "Group By"
+msgstr ""
+
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__internal_url
msgid "HTTP URL to load the file directly from storage."
@@ -228,6 +247,7 @@ msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__is_public
+#: model:ir.model.fields,field_description:storage_file.field_storage_file__is_public
msgid "Is Public"
msgstr ""
@@ -284,11 +304,26 @@ msgstr ""
msgid "Please select at least one file."
msgstr ""
+#. module: storage_file
+#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
+msgid "Private"
+msgstr ""
+
+#. module: storage_file
+#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
+msgid "Public"
+msgstr ""
+
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid "Recompute base URL for files"
msgstr ""
+#. module: storage_file
+#: model:ir.model.fields,help:storage_file.field_storage_file__is_public
+msgid "Reflects the `is_public` flag of the related backend."
+msgstr ""
+
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__relative_path
msgid "Relative Path"
diff --git a/storage_file/models/storage_backend.py b/storage_file/models/storage_backend.py
index c3a273a157..e011180b1b 100644
--- a/storage_file/models/storage_backend.py
+++ b/storage_file/models/storage_backend.py
@@ -145,7 +145,10 @@ def _get_base_url_from_param(self):
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":
+ # Make sure that no matter if you have a CDN URL or not,
+ # you can always access the file via Odoo.
+ force_serve_via_odoo = backend.served_by == "external" and not backend.base_url
+ if backend.served_by == "odoo" or force_serve_via_odoo:
parts = [
self._get_base_url_from_param() if not exclude_base_url else "/",
"storage.file",
diff --git a/storage_file/models/storage_file.py b/storage_file/models/storage_file.py
index f7f23614ec..b76c0d05e5 100644
--- a/storage_file/models/storage_file.py
+++ b/storage_file/models/storage_file.py
@@ -30,7 +30,11 @@ class StorageFile(models.Model):
name = fields.Char(required=True, index=True)
backend_id = fields.Many2one(
- "storage.backend", "Storage", index=True, required=True
+ "storage.backend",
+ "Storage",
+ index=True,
+ required=True,
+ default=lambda self: self._get_default_backend_id(),
)
url = fields.Char(compute="_compute_url", help="HTTP accessible path to the file")
url_path = fields.Char(
@@ -65,6 +69,33 @@ class StorageFile(models.Model):
"res.company", "Company", default=lambda self: self.env.user.company_id.id
)
file_type = fields.Selection([])
+ is_public = fields.Boolean(
+ compute="_compute_is_public",
+ compute_sudo=True,
+ search="_search_is_public",
+ # Not stored to avoid massive recomputes when the backend flag changes.
+ help="Reflects the `is_public` flag of the related backend.",
+ )
+
+ @api.depends("backend_id.is_public")
+ def _compute_is_public(self):
+ for rec in self:
+ rec.is_public = rec.backend_id.is_public
+
+ @api.model
+ def _get_default_backend_id(self):
+ return self.env["storage.backend"]._get_backend_id_from_param(
+ self.env, "storage.file.backend_id"
+ )
+
+ def _search_is_public(self, operator, value):
+ # Look up matching backends with sudo so that users with limited ACL
+ # on `storage.backend` can still filter their accessible files by
+ # public flag.
+ backends = (
+ self.env["storage.backend"].sudo().search([("is_public", operator, value)])
+ )
+ return [("backend_id", "in", backends.ids)]
_sql_constraints = [
(
@@ -175,6 +206,8 @@ def _get_url(self, exclude_base_url=False):
:param exclude_base_url: skip base_url
"""
+ if not self.backend_id or not self.relative_path:
+ return ""
return self.backend_id._get_url_for_file(
self, exclude_base_url=exclude_base_url
)
diff --git a/storage_file/security/ir.model.access.csv b/storage_file/security/ir.model.access.csv
index 4532fee45a..dce3e6034e 100644
--- a/storage_file/security/ir.model.access.csv
+++ b/storage_file/security/ir.model.access.csv
@@ -1,5 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_storage_file_edit,storage_file edit,model_storage_file,base.group_system,1,1,1,1
access_storage_file_read_public,storage_file public read,model_storage_file,base.group_user,1,0,0,0
+access_storage_file_read_portal,storage_file portal read,model_storage_file,base.group_public,1,0,0,0
access_storage_file_replace,storage_file_replace public,model_storage_file_replace,base.group_user,1,1,1,1
access_storage_file_swap_backend,storage_file_swap_backend admin,model_storage_file_swap_backend,base.group_system,1,1,1,1
diff --git a/storage_file/security/storage_file.xml b/storage_file/security/storage_file.xml
index 2510d3defb..bb09998341 100644
--- a/storage_file/security/storage_file.xml
+++ b/storage_file/security/storage_file.xml
@@ -13,4 +13,15 @@
+
+
+ Storage file internal read all
+
+
+ [(1, '=', 1)]
+
+
+
+
+
diff --git a/storage_file/static/description/index.html b/storage_file/static/description/index.html
index 50e1156ab5..8f336fd518 100644
--- a/storage_file/static/description/index.html
+++ b/storage_file/static/description/index.html
@@ -372,7 +372,7 @@
Storage File
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:14eff9ac3e34f90b55e98e5f1d8e938390db9e5d7c2f421d370143c3a26f5e1b
+!! source digest: sha256:c7ccd99c8428f973c93fbbdde0021b61a5db3077bc2d7722a2f929611f8ac415
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

External file management depending on Storage Backend module.
diff --git a/storage_file/tests/__init__.py b/storage_file/tests/__init__.py
index cdf4f78fa1..e20c8f1eea 100644
--- a/storage_file/tests/__init__.py
+++ b/storage_file/tests/__init__.py
@@ -1,2 +1,4 @@
from . import test_storage_file
from . import test_swap_backend
+from . import test_is_public
+from . import test_controller
diff --git a/storage_file/tests/test_controller.py b/storage_file/tests/test_controller.py
new file mode 100644
index 0000000000..511d987f54
--- /dev/null
+++ b/storage_file/tests/test_controller.py
@@ -0,0 +1,156 @@
+# Copyright 2026 Camptocamp SA
+# @author Simone Orsi
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+import base64
+
+from odoo.tests.common import HttpCase, tagged
+
+
+@tagged("-at_install", "post_install")
+class TestStorageFileController(HttpCase):
+ """Test the /storage.file/ controller with public/private and odoo/external."""
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
+ cls.data = b"Hello, storage!"
+ cls.filedata = base64.b64encode(cls.data)
+ cls.backend_odoo_public = cls.env["storage.backend"].create(
+ {
+ "name": "Odoo Public",
+ "backend_type": "filesystem",
+ "served_by": "odoo",
+ "is_public": True,
+ "filename_strategy": "name_with_id",
+ }
+ )
+ cls.backend_odoo_private = cls.env["storage.backend"].create(
+ {
+ "name": "Odoo Private",
+ "backend_type": "filesystem",
+ "served_by": "odoo",
+ "is_public": False,
+ "filename_strategy": "name_with_id",
+ }
+ )
+ cls.backend_ext_public = cls.env["storage.backend"].create(
+ {
+ "name": "Ext Public (no CDN)",
+ "backend_type": "filesystem",
+ "served_by": "external",
+ "base_url": "",
+ "is_public": True,
+ "filename_strategy": "name_with_id",
+ }
+ )
+ cls.backend_ext_private = cls.env["storage.backend"].create(
+ {
+ "name": "Ext Private (no CDN)",
+ "backend_type": "filesystem",
+ "served_by": "external",
+ "base_url": "",
+ "is_public": False,
+ "filename_strategy": "name_with_id",
+ }
+ )
+ cls.file_odoo_public = cls.env["storage.file"].create(
+ {
+ "name": "pub-odoo.txt",
+ "backend_id": cls.backend_odoo_public.id,
+ "data": cls.filedata,
+ }
+ )
+ cls.file_odoo_private = cls.env["storage.file"].create(
+ {
+ "name": "priv-odoo.txt",
+ "backend_id": cls.backend_odoo_private.id,
+ "data": cls.filedata,
+ }
+ )
+ cls.file_ext_public = cls.env["storage.file"].create(
+ {
+ "name": "pub-ext.txt",
+ "backend_id": cls.backend_ext_public.id,
+ "data": cls.filedata,
+ }
+ )
+ cls.file_ext_private = cls.env["storage.file"].create(
+ {
+ "name": "priv-ext.txt",
+ "backend_id": cls.backend_ext_private.id,
+ "data": cls.filedata,
+ }
+ )
+ cls.internal_user = (
+ cls.env["res.users"]
+ .with_context(no_reset_password=True)
+ .create(
+ {
+ "name": "Storage Test User",
+ "login": "storage_test_user",
+ "password": "storage_test_user",
+ "groups_id": [
+ (4, cls.env.ref("base.group_user").id),
+ ],
+ }
+ )
+ )
+
+ def _url_for(self, storage_file):
+ return f"/storage.file/{storage_file.slug}"
+
+ # ---- Public user (anonymous) ----
+
+ def test_public_user_odoo_public(self):
+ """Public user + public odoo backend -> 200."""
+ resp = self.url_open(self._url_for(self.file_odoo_public))
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.content, self.data)
+
+ def test_public_user_odoo_private(self):
+ """Public user + private odoo backend -> 404."""
+ resp = self.url_open(self._url_for(self.file_odoo_private))
+ self.assertEqual(resp.status_code, 404)
+
+ def test_public_user_ext_public(self):
+ """Public user + public external backend (no CDN) -> 200."""
+ resp = self.url_open(self._url_for(self.file_ext_public))
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.content, self.data)
+
+ def test_public_user_ext_private(self):
+ """Public user + private external backend (no CDN) -> 404."""
+ resp = self.url_open(self._url_for(self.file_ext_private))
+ self.assertEqual(resp.status_code, 404)
+
+ # ---- Internal (authenticated) user ----
+
+ def test_internal_user_odoo_public(self):
+ """Internal user + public odoo backend -> 200."""
+ self.authenticate("storage_test_user", "storage_test_user")
+ resp = self.url_open(self._url_for(self.file_odoo_public))
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.content, self.data)
+
+ def test_internal_user_odoo_private(self):
+ """Internal user + private odoo backend -> 200."""
+ self.authenticate("storage_test_user", "storage_test_user")
+ resp = self.url_open(self._url_for(self.file_odoo_private))
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.content, self.data)
+
+ def test_internal_user_ext_public(self):
+ """Internal user + public external backend (no CDN) -> 200."""
+ self.authenticate("storage_test_user", "storage_test_user")
+ resp = self.url_open(self._url_for(self.file_ext_public))
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.content, self.data)
+
+ def test_internal_user_ext_private(self):
+ """Internal user + private external backend (no CDN) -> 200."""
+ self.authenticate("storage_test_user", "storage_test_user")
+ resp = self.url_open(self._url_for(self.file_ext_private))
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.content, self.data)
diff --git a/storage_file/tests/test_is_public.py b/storage_file/tests/test_is_public.py
new file mode 100644
index 0000000000..7f93df7163
--- /dev/null
+++ b/storage_file/tests/test_is_public.py
@@ -0,0 +1,59 @@
+# Copyright 2026 Camptocamp SA
+# @author Simone Orsi
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+from odoo.addons.component.tests.common import TransactionComponentCase
+
+
+class TestStorageFileIsPublic(TransactionComponentCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.backend = cls.env["storage.backend"].create(
+ {"name": "Test backend", "backend_type": "filesystem"}
+ )
+ cls.storage_file = cls.env["storage.file"].create(
+ {
+ "name": "test-public.txt",
+ "backend_id": cls.backend.id,
+ "data": b"aGVsbG8=", # "hello" base64
+ }
+ )
+
+ def test_reflects_backend_flag(self):
+ self.assertFalse(self.storage_file.is_public)
+ self.backend.is_public = True
+ self.assertTrue(self.storage_file.is_public)
+
+ def test_search_true(self):
+ self.backend.is_public = True
+ result = self.env["storage.file"].search(
+ [("is_public", "=", True), ("id", "=", self.storage_file.id)]
+ )
+ self.assertIn(self.storage_file, result)
+
+ def test_search_false(self):
+ self.backend.is_public = False
+ result = self.env["storage.file"].search(
+ [("is_public", "=", False), ("id", "=", self.storage_file.id)]
+ )
+ self.assertIn(self.storage_file, result)
+
+ def test_search_with_two_backends(self):
+ public_backend = self.env["storage.backend"].create(
+ {
+ "name": "Public backend",
+ "backend_type": "filesystem",
+ "is_public": True,
+ }
+ )
+ public_file = self.env["storage.file"].create(
+ {
+ "name": "public.txt",
+ "backend_id": public_backend.id,
+ "data": b"aGVsbG8=",
+ }
+ )
+ result = self.env["storage.file"].search([("is_public", "=", True)])
+ self.assertIn(public_file, result)
+ self.assertNotIn(self.storage_file, result)
diff --git a/storage_file/tests/test_storage_file.py b/storage_file/tests/test_storage_file.py
index fa540cbcf9..46a3084d7e 100644
--- a/storage_file/tests/test_storage_file.py
+++ b/storage_file/tests/test_storage_file.py
@@ -7,6 +7,7 @@
from urllib import parse
from odoo.exceptions import AccessError, UserError
+from odoo.tests import Form
from odoo.addons.component.tests.common import TransactionComponentCase
@@ -110,6 +111,17 @@ def test_url(self):
stfile.url, f"https://foo.com/baz/test-of-my_file-{stfile.id}.txt"
)
+ def test_url_external_no_base_url_falls_back_to_odoo(self):
+ """External backend w/o base_url uses the internal Odoo route."""
+ stfile = self._create_storage_file()
+ params = self.env["ir.config_parameter"].sudo()
+ base_url = params.get_param("web.base.url")
+ stfile.backend_id.update({"served_by": "external", "base_url": ""})
+ self.assertEqual(
+ stfile.url,
+ f"{base_url}/storage.file/test-of-my_file-{stfile.id}.txt",
+ )
+
def test_url_without_base_url(self):
stfile = self._create_storage_file()
# served by odoo
@@ -306,3 +318,23 @@ def test_empty(self):
# get_url is called on new records
empty = self.env["storage.file"].new({})._get_url()
self.assertEqual(empty, "")
+
+ def test_default_backend_id_on_form(self):
+ """Form pre-fills the default backend for storage.file."""
+ form = Form(self.env["storage.file"])
+ self.assertEqual(form.backend_id, self.backend)
+
+ def test_default_backend_id_from_param(self):
+ """storage.file.backend_id param overrides the form default."""
+ other_backend = self.env["storage.backend"].create(
+ {
+ "name": "Other",
+ "backend_type": "filesystem",
+ "filename_strategy": "name_with_id",
+ }
+ )
+ self.env["ir.config_parameter"].sudo().set_param(
+ "storage.file.backend_id", str(other_backend.id)
+ )
+ form = Form(self.env["storage.file"])
+ self.assertEqual(form.backend_id, other_backend)
diff --git a/storage_file/views/storage_backend_view.xml b/storage_file/views/storage_backend_view.xml
index 219cd3fc84..31e4ceb56b 100644
--- a/storage_file/views/storage_backend_view.xml
+++ b/storage_file/views/storage_backend_view.xml
@@ -15,8 +15,18 @@
Make sure this parameter is properly configured and accessible
from everwhere you want to access the service.
-
+
+
+ Note: Without a CDN base URL, files will be proxied
+ through Odoo. This is acceptable for internal use with low traffic
+ but suboptimal for high-volume public access as every request loads
+ the file from the storage backend into memory.
+
diff --git a/storage_file/views/storage_file_view.xml b/storage_file/views/storage_file_view.xml
index 7405b367b8..a922416d1d 100644
--- a/storage_file/views/storage_file_view.xml
+++ b/storage_file/views/storage_file_view.xml
@@ -8,6 +8,7 @@
+
@@ -29,6 +30,7 @@
+
@@ -42,6 +44,24 @@
+
+
+
+
+
+
diff --git a/storage_image/README.rst b/storage_image/README.rst
index b11178c3c2..b119ddd1e8 100644
--- a/storage_image/README.rst
+++ b/storage_image/README.rst
@@ -11,7 +11,7 @@ Storage Image
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:288cb6384d178a0baa57c73bf92d5ab94f325c0ff7e347bd72c79da2d67e18fa
+ !! source digest: sha256:5d665efc3d0b5c44f7dec44b33a76b1f1d53557ca28803cdaf1613f2814bb73b
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
diff --git a/storage_image/__manifest__.py b/storage_image/__manifest__.py
index 39063806b7..e91a2d1dd4 100644
--- a/storage_image/__manifest__.py
+++ b/storage_image/__manifest__.py
@@ -5,7 +5,7 @@
{
"name": "Storage Image",
"summary": "Store image and resized image in a storage backend",
- "version": "18.0.1.1.0",
+ "version": "18.0.1.2.0",
"category": "Storage",
"website": "https://github.com/OCA/storage",
"author": " Akretion, Odoo Community Association (OCA)",
diff --git a/storage_image/i18n/it.po b/storage_image/i18n/it.po
index 8c6e824569..d779ad2cf3 100644
--- a/storage_image/i18n/it.po
+++ b/storage_image/i18n/it.po
@@ -32,6 +32,7 @@ msgid "Accessible path, no base URL"
msgstr "Percorso accessibile, non URL base"
#. module: storage_image
+#: model:ir.model.fields,field_description:storage_image.field_image_relation_abstract__active
#: model:ir.model.fields,field_description:storage_image.field_storage_image__active
msgid "Active"
msgstr "Attiva"
@@ -42,6 +43,11 @@ msgstr "Attiva"
msgid "Alt Image name"
msgstr "Nome alternativo immagine"
+#. module: storage_image
+#: model_terms:ir.ui.view,arch_db:storage_image.storage_image_view_search
+msgid "Backend"
+msgstr ""
+
#. module: storage_image
#: model_terms:ir.ui.view,arch_db:storage_image.storage_file_replace_view_form
msgid "Cancel"
@@ -115,6 +121,11 @@ msgstr "Tipo file"
msgid "Filename without extension"
msgstr "Nome file senza estensione"
+#. module: storage_image
+#: model_terms:ir.ui.view,arch_db:storage_image.storage_image_view_search
+msgid "Group By"
+msgstr ""
+
#. module: storage_image
#: model:ir.model.fields,help:storage_image.field_storage_image__internal_url
msgid "HTTP URL to load the file directly from storage."
@@ -162,6 +173,12 @@ msgstr "Sintesi relazione immagine"
msgid "Internal Url"
msgstr "URL interno"
+#. module: storage_image
+#: model:ir.model.fields,field_description:storage_image.field_image_relation_abstract__is_public
+#: model:ir.model.fields,field_description:storage_image.field_storage_image__is_public
+msgid "Is Public"
+msgstr ""
+
#. module: storage_image
#: model:ir.model.fields,field_description:storage_image.field_storage_image__write_uid
msgid "Last Updated by"
@@ -191,12 +208,28 @@ msgstr "Tipo MIME"
msgid "Name"
msgstr "Nome"
+#. module: storage_image
+#: model_terms:ir.ui.view,arch_db:storage_image.storage_image_view_search
+msgid "Private"
+msgstr ""
+
+#. module: storage_image
+#: model_terms:ir.ui.view,arch_db:storage_image.storage_image_view_search
+msgid "Public"
+msgstr ""
+
#. module: storage_image
#. odoo-javascript
#: code:addons/storage_image/static/src/js/StorageImageHandle.esm.js:0
msgid "Records have been successfully rearranged."
msgstr "I record sono stati riorganizzati con successo."
+#. module: storage_image
+#: model:ir.model.fields,help:storage_image.field_image_relation_abstract__is_public
+#: model:ir.model.fields,help:storage_image.field_storage_image__is_public
+msgid "Reflects the `is_public` flag of the related backend."
+msgstr ""
+
#. module: storage_image
#: model:ir.model.fields,field_description:storage_image.field_storage_image__relative_path
msgid "Relative Path"
diff --git a/storage_image/i18n/storage_image.pot b/storage_image/i18n/storage_image.pot
index 8fdd8cf1fe..c8dc2ae4e6 100644
--- a/storage_image/i18n/storage_image.pot
+++ b/storage_image/i18n/storage_image.pot
@@ -29,6 +29,7 @@ msgid "Accessible path, no base URL"
msgstr ""
#. module: storage_image
+#: model:ir.model.fields,field_description:storage_image.field_image_relation_abstract__active
#: model:ir.model.fields,field_description:storage_image.field_storage_image__active
msgid "Active"
msgstr ""
@@ -39,6 +40,11 @@ msgstr ""
msgid "Alt Image name"
msgstr ""
+#. module: storage_image
+#: model_terms:ir.ui.view,arch_db:storage_image.storage_image_view_search
+msgid "Backend"
+msgstr ""
+
#. module: storage_image
#: model_terms:ir.ui.view,arch_db:storage_image.storage_file_replace_view_form
msgid "Cancel"
@@ -112,6 +118,11 @@ msgstr ""
msgid "Filename without extension"
msgstr ""
+#. module: storage_image
+#: model_terms:ir.ui.view,arch_db:storage_image.storage_image_view_search
+msgid "Group By"
+msgstr ""
+
#. module: storage_image
#: model:ir.model.fields,help:storage_image.field_storage_image__internal_url
msgid "HTTP URL to load the file directly from storage."
@@ -159,6 +170,12 @@ msgstr ""
msgid "Internal Url"
msgstr ""
+#. module: storage_image
+#: model:ir.model.fields,field_description:storage_image.field_image_relation_abstract__is_public
+#: model:ir.model.fields,field_description:storage_image.field_storage_image__is_public
+msgid "Is Public"
+msgstr ""
+
#. module: storage_image
#: model:ir.model.fields,field_description:storage_image.field_storage_image__write_uid
msgid "Last Updated by"
@@ -188,12 +205,28 @@ msgstr ""
msgid "Name"
msgstr ""
+#. module: storage_image
+#: model_terms:ir.ui.view,arch_db:storage_image.storage_image_view_search
+msgid "Private"
+msgstr ""
+
+#. module: storage_image
+#: model_terms:ir.ui.view,arch_db:storage_image.storage_image_view_search
+msgid "Public"
+msgstr ""
+
#. module: storage_image
#. odoo-javascript
#: code:addons/storage_image/static/src/js/StorageImageHandle.esm.js:0
msgid "Records have been successfully rearranged."
msgstr ""
+#. module: storage_image
+#: model:ir.model.fields,help:storage_image.field_image_relation_abstract__is_public
+#: model:ir.model.fields,help:storage_image.field_storage_image__is_public
+msgid "Reflects the `is_public` flag of the related backend."
+msgstr ""
+
#. module: storage_image
#: model:ir.model.fields,field_description:storage_image.field_storage_image__relative_path
msgid "Relative Path"
diff --git a/storage_image/models/storage_image_relation_abstract.py b/storage_image/models/storage_image_relation_abstract.py
index 0c6272227f..4f0723a5a4 100644
--- a/storage_image/models/storage_image_relation_abstract.py
+++ b/storage_image/models/storage_image_relation_abstract.py
@@ -23,3 +23,5 @@ class ImageRelationAbstract(models.AbstractModel):
image_name = fields.Char(related="image_id.name")
image_alt_name = fields.Char(related="image_id.alt_name")
image_url = fields.Char(related="image_id.image_medium_url")
+ is_public = fields.Boolean(related="image_id.file_id.is_public", readonly=True)
+ active = fields.Boolean(related="image_id.active", readonly=True)
diff --git a/storage_image/static/description/index.html b/storage_image/static/description/index.html
index 32df5a2856..da1d4dfef7 100644
--- a/storage_image/static/description/index.html
+++ b/storage_image/static/description/index.html
@@ -372,7 +372,7 @@ Storage Image
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:288cb6384d178a0baa57c73bf92d5ab94f325c0ff7e347bd72c79da2d67e18fa
+!! source digest: sha256:5d665efc3d0b5c44f7dec44b33a76b1f1d53557ca28803cdaf1613f2814bb73b
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

External image management depending on Storage File module.
diff --git a/storage_image/tests/__init__.py b/storage_image/tests/__init__.py
index cedd944b24..fbe7e719dc 100644
--- a/storage_image/tests/__init__.py
+++ b/storage_image/tests/__init__.py
@@ -2,3 +2,4 @@
from . import test_storage_image
from . import test_storage_replace_file
from . import test_swap_backend
+from . import test_is_public
diff --git a/storage_image/tests/test_is_public.py b/storage_image/tests/test_is_public.py
new file mode 100644
index 0000000000..27938492d7
--- /dev/null
+++ b/storage_image/tests/test_is_public.py
@@ -0,0 +1,25 @@
+# Copyright 2026 Camptocamp SA
+# @author Simone Orsi
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+from .common import StorageImageCommonCase
+
+
+class TestStorageImageIsPublic(StorageImageCommonCase):
+ def setUp(self):
+ super().setUp()
+ self.storage_image = self._create_storage_image_from_file(
+ "static/akretion-logo.png"
+ )
+
+ def test_is_public_reflects_backend(self):
+ self.assertFalse(self.storage_image.is_public)
+ self.backend.sudo().is_public = True
+ self.assertTrue(self.storage_image.is_public)
+
+ def test_is_public_search(self):
+ self.backend.sudo().is_public = True
+ result = self.env["storage.image"].search(
+ [("is_public", "=", True), ("id", "=", self.storage_image.id)]
+ )
+ self.assertEqual(self.storage_image, result)
diff --git a/storage_image/tests/test_storage_image.py b/storage_image/tests/test_storage_image.py
index de3f128065..bccce9c4ed 100644
--- a/storage_image/tests/test_storage_image.py
+++ b/storage_image/tests/test_storage_image.py
@@ -115,3 +115,27 @@ def test_create_thumbnail_pilbox(self):
"&w=64&h=64&mode=fill&fmt=webp",
urls,
)
+
+ def test_default_backend_id_on_form(self):
+ """Creating an image without backend_id uses the configured default."""
+ image = self._create_storage_image(self.filename, self.filedata)
+ self.assertEqual(image.backend_id, self.backend)
+
+ def test_default_backend_id_from_param(self):
+ """storage.image.backend_id param overrides backend on create."""
+ other_backend = (
+ self.env["storage.backend"]
+ .sudo()
+ .create(
+ {
+ "name": "Image Backend",
+ "backend_type": "filesystem",
+ "filename_strategy": "name_with_id",
+ }
+ )
+ )
+ self.env["ir.config_parameter"].sudo().set_param(
+ "storage.image.backend_id", str(other_backend.id)
+ )
+ image = self._create_storage_image(self.filename, self.filedata)
+ self.assertEqual(image.backend_id.id, other_backend.id)
diff --git a/storage_image/views/storage_image.xml b/storage_image/views/storage_image.xml
index f05dda0e0b..42338506e2 100644
--- a/storage_image/views/storage_image.xml
+++ b/storage_image/views/storage_image.xml
@@ -8,6 +8,7 @@
+
@@ -39,6 +40,7 @@
+
@@ -86,6 +88,25 @@
+
+
+
+
+
+
+
diff --git a/storage_image_product/README.rst b/storage_image_product/README.rst
index f32d81c587..f0743f2f44 100644
--- a/storage_image_product/README.rst
+++ b/storage_image_product/README.rst
@@ -11,7 +11,7 @@ Storage Image Product
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:da4b914be20e01a01cfa782549a9aeab2ac2a946a96895fce85de92e6f2b454c
+ !! source digest: sha256:9771b16b89fdd6826cf5a52c8acd51d7e53786b76a566f42d8662e7a140a9d9d
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
diff --git a/storage_image_product/__manifest__.py b/storage_image_product/__manifest__.py
index 7eaac02f1e..cea1e84d99 100644
--- a/storage_image_product/__manifest__.py
+++ b/storage_image_product/__manifest__.py
@@ -5,7 +5,7 @@
{
"name": "Storage Image Product",
"summary": "Link images to products and categories",
- "version": "18.0.1.0.2",
+ "version": "18.0.1.1.0",
"category": "Storage",
"website": "https://github.com/OCA/storage",
"author": " Akretion, Odoo Community Association (OCA)",
diff --git a/storage_image_product/i18n/it.po b/storage_image_product/i18n/it.po
index 11a28e5947..38a0411361 100644
--- a/storage_image_product/i18n/it.po
+++ b/storage_image_product/i18n/it.po
@@ -26,6 +26,12 @@ msgstr "N° di categorie"
msgid "# of Products"
msgstr "N° di prodotti"
+#. module: storage_image_product
+#: model:ir.model.fields,field_description:storage_image_product.field_category_image_relation__active
+#: model:ir.model.fields,field_description:storage_image_product.field_product_image_relation__active
+msgid "Active"
+msgstr ""
+
#. module: storage_image_product
#: model:ir.model.fields,field_description:storage_image_product.field_category_image_relation__image_alt_name
#: model:ir.model.fields,field_description:storage_image_product.field_product_image_relation__image_alt_name
@@ -135,6 +141,12 @@ msgstr "Etichetta immagine"
msgid "Images"
msgstr "Immagini"
+#. module: storage_image_product
+#: model:ir.model.fields,field_description:storage_image_product.field_category_image_relation__is_public
+#: model:ir.model.fields,field_description:storage_image_product.field_product_image_relation__is_public
+msgid "Is Public"
+msgstr ""
+
#. module: storage_image_product
#: model:ir.model.fields,field_description:storage_image_product.field_category_image_relation__write_uid
#: model:ir.model.fields,field_description:storage_image_product.field_image_tag__write_uid
@@ -231,6 +243,12 @@ msgstr "Variante prodotto"
msgid "Products"
msgstr "Prodotti"
+#. module: storage_image_product
+#: model:ir.model.fields,help:storage_image_product.field_category_image_relation__is_public
+#: model:ir.model.fields,help:storage_image_product.field_product_image_relation__is_public
+msgid "Reflects the `is_public` flag of the related backend."
+msgstr ""
+
#. module: storage_image_product
#: model:ir.model.fields,field_description:storage_image_product.field_category_image_relation__sequence
#: model:ir.model.fields,field_description:storage_image_product.field_product_image_relation__sequence
diff --git a/storage_image_product/i18n/storage_image_product.pot b/storage_image_product/i18n/storage_image_product.pot
index bf1bc5c21f..25a837a4b2 100644
--- a/storage_image_product/i18n/storage_image_product.pot
+++ b/storage_image_product/i18n/storage_image_product.pot
@@ -23,6 +23,12 @@ msgstr ""
msgid "# of Products"
msgstr ""
+#. module: storage_image_product
+#: model:ir.model.fields,field_description:storage_image_product.field_category_image_relation__active
+#: model:ir.model.fields,field_description:storage_image_product.field_product_image_relation__active
+msgid "Active"
+msgstr ""
+
#. module: storage_image_product
#: model:ir.model.fields,field_description:storage_image_product.field_category_image_relation__image_alt_name
#: model:ir.model.fields,field_description:storage_image_product.field_product_image_relation__image_alt_name
@@ -132,6 +138,12 @@ msgstr ""
msgid "Images"
msgstr ""
+#. module: storage_image_product
+#: model:ir.model.fields,field_description:storage_image_product.field_category_image_relation__is_public
+#: model:ir.model.fields,field_description:storage_image_product.field_product_image_relation__is_public
+msgid "Is Public"
+msgstr ""
+
#. module: storage_image_product
#: model:ir.model.fields,field_description:storage_image_product.field_category_image_relation__write_uid
#: model:ir.model.fields,field_description:storage_image_product.field_image_tag__write_uid
@@ -228,6 +240,12 @@ msgstr ""
msgid "Products"
msgstr ""
+#. module: storage_image_product
+#: model:ir.model.fields,help:storage_image_product.field_category_image_relation__is_public
+#: model:ir.model.fields,help:storage_image_product.field_product_image_relation__is_public
+msgid "Reflects the `is_public` flag of the related backend."
+msgstr ""
+
#. module: storage_image_product
#: model:ir.model.fields,field_description:storage_image_product.field_category_image_relation__sequence
#: model:ir.model.fields,field_description:storage_image_product.field_product_image_relation__sequence
diff --git a/storage_image_product/static/description/index.html b/storage_image_product/static/description/index.html
index e5e73410d7..c970f5b821 100644
--- a/storage_image_product/static/description/index.html
+++ b/storage_image_product/static/description/index.html
@@ -372,7 +372,7 @@ Storage Image Product
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:da4b914be20e01a01cfa782549a9aeab2ac2a946a96895fce85de92e6f2b454c
+!! source digest: sha256:9771b16b89fdd6826cf5a52c8acd51d7e53786b76a566f42d8662e7a140a9d9d
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Attach images to products and categories
diff --git a/storage_image_product/views/storage_image.xml b/storage_image_product/views/storage_image.xml
index c93cd0fdb2..c3a4cd140f 100644
--- a/storage_image_product/views/storage_image.xml
+++ b/storage_image_product/views/storage_image.xml
@@ -7,11 +7,17 @@
-
+
+
+
-
+
+
+
@@ -34,6 +35,7 @@
+
@@ -45,8 +47,20 @@
+
+
+
+
+
+
+
diff --git a/storage_media_product/README.rst b/storage_media_product/README.rst
index a59bde9257..3b64bd8b60 100644
--- a/storage_media_product/README.rst
+++ b/storage_media_product/README.rst
@@ -11,7 +11,7 @@ Storage Media Product
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:1ebaac1a52437a2cde37c3583656a9c9185ac8e24068cf876aee4e93b654460d
+ !! source digest: sha256:3a5eabb371a6add760b7a4453df1247677c0efb1fab103fc5ae07e651a4bb499
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/storage_media_product/__manifest__.py b/storage_media_product/__manifest__.py
index 43642eb1d2..52919ec988 100644
--- a/storage_media_product/__manifest__.py
+++ b/storage_media_product/__manifest__.py
@@ -5,7 +5,7 @@
{
"name": "Storage Media Product",
"summary": "Link media to products and categories",
- "version": "18.0.1.0.1",
+ "version": "18.0.1.1.0",
"category": "Storage",
"website": "https://github.com/OCA/storage",
"author": " Akretion, Odoo Community Association (OCA)",
diff --git a/storage_media_product/i18n/it.po b/storage_media_product/i18n/it.po
index b60968ce71..493abaed27 100644
--- a/storage_media_product/i18n/it.po
+++ b/storage_media_product/i18n/it.po
@@ -21,6 +21,11 @@ msgstr ""
msgid "Accessible path, no base URL"
msgstr "Percorso accessibile, non URL base"
+#. module: storage_media_product
+#: model:ir.model.fields,field_description:storage_media_product.field_product_media_relation__active
+msgid "Active"
+msgstr ""
+
#. module: storage_media_product
#: model_terms:ir.ui.view,arch_db:storage_media_product.product_media_relation_form
msgid "Association"
@@ -66,6 +71,11 @@ msgstr "Percorso HTTP al file accessibile"
msgid "ID"
msgstr "ID"
+#. module: storage_media_product
+#: model:ir.model.fields,field_description:storage_media_product.field_product_media_relation__is_public
+msgid "Is Public"
+msgstr ""
+
#. module: storage_media_product
#: model:ir.model.fields,field_description:storage_media_product.field_product_media_relation__write_uid
msgid "Last Updated by"
@@ -122,6 +132,11 @@ msgstr "Variante prodotto"
msgid "Products and storage media relation"
msgstr "Relazione prodotti e media deposito"
+#. module: storage_media_product
+#: model:ir.model.fields,help:storage_media_product.field_product_media_relation__is_public
+msgid "Reflects the `is_public` flag of the related backend."
+msgstr ""
+
#. module: storage_media_product
#: model:ir.model.fields,field_description:storage_media_product.field_product_media_relation__sequence
msgid "Sequence"
diff --git a/storage_media_product/i18n/storage_media_product.pot b/storage_media_product/i18n/storage_media_product.pot
index 975eef8bd7..796bc54628 100644
--- a/storage_media_product/i18n/storage_media_product.pot
+++ b/storage_media_product/i18n/storage_media_product.pot
@@ -18,6 +18,11 @@ msgstr ""
msgid "Accessible path, no base URL"
msgstr ""
+#. module: storage_media_product
+#: model:ir.model.fields,field_description:storage_media_product.field_product_media_relation__active
+msgid "Active"
+msgstr ""
+
#. module: storage_media_product
#: model_terms:ir.ui.view,arch_db:storage_media_product.product_media_relation_form
msgid "Association"
@@ -63,6 +68,11 @@ msgstr ""
msgid "ID"
msgstr ""
+#. module: storage_media_product
+#: model:ir.model.fields,field_description:storage_media_product.field_product_media_relation__is_public
+msgid "Is Public"
+msgstr ""
+
#. module: storage_media_product
#: model:ir.model.fields,field_description:storage_media_product.field_product_media_relation__write_uid
msgid "Last Updated by"
@@ -119,6 +129,11 @@ msgstr ""
msgid "Products and storage media relation"
msgstr ""
+#. module: storage_media_product
+#: model:ir.model.fields,help:storage_media_product.field_product_media_relation__is_public
+msgid "Reflects the `is_public` flag of the related backend."
+msgstr ""
+
#. module: storage_media_product
#: model:ir.model.fields,field_description:storage_media_product.field_product_media_relation__sequence
msgid "Sequence"
diff --git a/storage_media_product/models/product.py b/storage_media_product/models/product.py
index 12832b70af..a885f7f80d 100644
--- a/storage_media_product/models/product.py
+++ b/storage_media_product/models/product.py
@@ -65,6 +65,8 @@ class ProductMediaRelation(models.Model):
url = fields.Char(related="media_id.url", readonly=True)
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)
@api.depends("media_id", "product_tmpl_id.attribute_line_ids.value_ids")
def _compute_available_attribute(self):
diff --git a/storage_media_product/static/description/index.html b/storage_media_product/static/description/index.html
index ee918351b4..8947d3bcc1 100644
--- a/storage_media_product/static/description/index.html
+++ b/storage_media_product/static/description/index.html
@@ -372,7 +372,7 @@ Storage Media Product
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:1ebaac1a52437a2cde37c3583656a9c9185ac8e24068cf876aee4e93b654460d
+!! source digest: sha256:3a5eabb371a6add760b7a4453df1247677c0efb1fab103fc5ae07e651a4bb499
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Attach images to products and categories
diff --git a/storage_media_product/views/product.xml b/storage_media_product/views/product.xml
index 66095eb3ea..d03831bbfa 100644
--- a/storage_media_product/views/product.xml
+++ b/storage_media_product/views/product.xml
@@ -26,6 +26,7 @@
widget="many2many_tags"
invisible="not available_attribute_value_ids"
/>
+
@@ -34,9 +35,15 @@
product.media.relation
-
+
+
+
diff --git a/storage_thumbnail/README.rst b/storage_thumbnail/README.rst
index a2c5d9cde4..cf6bd62305 100644
--- a/storage_thumbnail/README.rst
+++ b/storage_thumbnail/README.rst
@@ -1,3 +1,7 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
=================
Storage Thumbnail
=================
@@ -7,13 +11,13 @@ Storage Thumbnail
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:3321f6180032d0f285bf859786259d133e995f1bf60c2d1250df0805789fc83f
+ !! source digest: sha256:7e31e0b9d9261fe9d529753f5d57dfaafffbf51dbdd6fffdccb52382e26bcc63
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
:target: https://odoo-community.org/page/development-status
:alt: Production/Stable
-.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
+.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstorage-lightgray.png?logo=github
diff --git a/storage_thumbnail/__manifest__.py b/storage_thumbnail/__manifest__.py
index 14570c6693..8cabd67fe5 100644
--- a/storage_thumbnail/__manifest__.py
+++ b/storage_thumbnail/__manifest__.py
@@ -5,7 +5,7 @@
{
"name": "Storage Thumbnail",
"summary": "Abstract module that add the possibility to have thumbnail",
- "version": "18.0.1.0.0",
+ "version": "18.0.1.1.0",
"category": "Storage",
"website": "https://github.com/OCA/storage",
"author": " Akretion, Odoo Community Association (OCA)",
diff --git a/storage_thumbnail/i18n/it.po b/storage_thumbnail/i18n/it.po
index 596b0b9776..5403a71050 100644
--- a/storage_thumbnail/i18n/it.po
+++ b/storage_thumbnail/i18n/it.po
@@ -108,6 +108,11 @@ msgstr "ID"
msgid "Internal Url"
msgstr "URL interno"
+#. module: storage_thumbnail
+#: model:ir.model.fields,field_description:storage_thumbnail.field_storage_thumbnail__is_public
+msgid "Is Public"
+msgstr ""
+
#. module: storage_thumbnail
#: model:ir.model.fields,field_description:storage_thumbnail.field_storage_thumbnail__write_uid
msgid "Last Updated by"
@@ -133,6 +138,11 @@ msgstr "Tipo MIME"
msgid "Name"
msgstr "Nome"
+#. module: storage_thumbnail
+#: model:ir.model.fields,help:storage_thumbnail.field_storage_thumbnail__is_public
+msgid "Reflects the `is_public` flag of the related backend."
+msgstr ""
+
#. module: storage_thumbnail
#: model:ir.model.fields,field_description:storage_thumbnail.field_storage_thumbnail__relative_path
msgid "Relative Path"
diff --git a/storage_thumbnail/i18n/storage_thumbnail.pot b/storage_thumbnail/i18n/storage_thumbnail.pot
index e11335dcf2..adc4158782 100644
--- a/storage_thumbnail/i18n/storage_thumbnail.pot
+++ b/storage_thumbnail/i18n/storage_thumbnail.pot
@@ -105,6 +105,11 @@ msgstr ""
msgid "Internal Url"
msgstr ""
+#. module: storage_thumbnail
+#: model:ir.model.fields,field_description:storage_thumbnail.field_storage_thumbnail__is_public
+msgid "Is Public"
+msgstr ""
+
#. module: storage_thumbnail
#: model:ir.model.fields,field_description:storage_thumbnail.field_storage_thumbnail__write_uid
msgid "Last Updated by"
@@ -130,6 +135,11 @@ msgstr ""
msgid "Name"
msgstr ""
+#. module: storage_thumbnail
+#: model:ir.model.fields,help:storage_thumbnail.field_storage_thumbnail__is_public
+msgid "Reflects the `is_public` flag of the related backend."
+msgstr ""
+
#. module: storage_thumbnail
#: model:ir.model.fields,field_description:storage_thumbnail.field_storage_thumbnail__relative_path
msgid "Relative Path"
diff --git a/storage_thumbnail/static/description/index.html b/storage_thumbnail/static/description/index.html
index 70de439c32..8b7167ce3b 100644
--- a/storage_thumbnail/static/description/index.html
+++ b/storage_thumbnail/static/description/index.html
@@ -3,7 +3,7 @@
-Storage Thumbnail
+README.rst
-
-
Storage Thumbnail
+
+
+
+
+
+
+
Storage Thumbnail
-

+

External image thumbnail management depending on Storage File module.
Table of contents
@@ -385,7 +390,7 @@
Storage Thumbnail
-
+
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
@@ -393,15 +398,15 @@
Do not contact contributors directly about support or help with technical issues.
-
+
-
+
The migration of this module from 15.0 to 18.0 was financially supported
by Camptocamp
-
+
This module is maintained by the OCA.
@@ -428,5 +433,6 @@
+