Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3a8bc41
[IMP] storage_file: add search by public/private
simahawk May 21, 2026
363ad50
[IMP] storage_image: add search by public/private
simahawk May 21, 2026
588bafa
[IMP] storage_media: add search by public/private
simahawk May 21, 2026
1298278
[IMP] storage_media_product: show is_public
simahawk May 24, 2026
9635392
[IMP] storage_file: make is_public visible all the times
simahawk May 27, 2026
a15e667
[IMP] storage_file: allow viewing external files via Odoo
simahawk May 28, 2026
b29b208
[IMP] storage_file: edit backend and pre-load default
simahawk May 28, 2026
d4959cc
[FIX] storage_file: _get_url returns nothing if no backend is set
simahawk May 28, 2026
7b339a5
[FIX] storage_thumbnail: test data
simahawk May 28, 2026
71ccf1b
[IMP] storage_file: allow to group by backend
simahawk Jun 10, 2026
5675d61
[IMP] storage_image*: improve views
simahawk Jun 10, 2026
1c9ae67
[IMP] storage_media*: improve views
simahawk Jun 10, 2026
fcb16d8
checklog-odoo: ignore warning from assets generation
simahawk Jun 10, 2026
c5baaf2
Merge PR #623 into 18.0
OCA-git-bot Jun 12, 2026
dc54dce
[UPD] Update storage_file.pot
Jun 12, 2026
ff40a6d
[UPD] Update storage_image.pot
Jun 12, 2026
4d3ea78
[UPD] Update storage_image_product.pot
Jun 12, 2026
a6dbf76
[UPD] Update storage_media.pot
Jun 12, 2026
1657e96
[UPD] Update storage_media_product.pot
Jun 12, 2026
10f070c
[UPD] Update storage_thumbnail.pot
Jun 12, 2026
3ebaef2
[BOT] post-merge updates
OCA-git-bot Jun 12, 2026
46c7188
Update translation files
weblate Jun 12, 2026
4b1c739
Update translation files
weblate Jun 12, 2026
04adccf
Update translation files
weblate Jun 12, 2026
c0a43c3
Update translation files
weblate Jun 12, 2026
42af5de
Update translation files
weblate Jun 12, 2026
97cf807
Update translation files
weblate Jun 12, 2026
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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | <a href='https://github.com/simahawk'><img src='https://github.com/simahawk.png' width='32' height='32' style='border-radius:50%;' alt='simahawk'/></a> | 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)

Expand Down
1 change: 1 addition & 0 deletions checklog-odoo.cfg
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion storage_file/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion storage_file/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand All @@ -23,5 +23,6 @@
"data/ir_cron.xml",
"data/storage_backend.xml",
"wizards/swap_backend.xml",
"data/ir_config_parameter.xml",
],
}
11 changes: 11 additions & 0 deletions storage_file/controllers/main.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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"
)
Expand Down
7 changes: 7 additions & 0 deletions storage_file/data/ir_config_parameter.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="storage_file_backend" model="ir.config_parameter">
<field name="key">storage.file.backend_id</field>
<field name="value" ref="storage_backend.default_storage_backend" />
</record>
</odoo>
37 changes: 37 additions & 0 deletions storage_file/i18n/it.po
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
"<strong>Note:</strong> 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
Expand All @@ -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"
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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"
Expand Down
35 changes: 35 additions & 0 deletions storage_file/i18n/storage_file.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
"<strong>Note:</strong> 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
Expand All @@ -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"
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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 ""

Expand Down Expand Up @@ -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"
Expand Down
5 changes: 4 additions & 1 deletion storage_file/models/storage_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
35 changes: 34 additions & 1 deletion storage_file/models/storage_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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 = [
(
Expand Down Expand Up @@ -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
)
Expand Down
1 change: 1 addition & 0 deletions storage_file/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions storage_file/security/storage_file.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,15 @@
<field name="perm_create" eval="False" />
<field name="perm_unlink" eval="False" />
</record>
<!--Internal users can read all files (public and private)-->
<record id="ir_rule_storage_file_internal" model="ir.rule">
<field name="name">Storage file internal read all</field>
<field name="model_id" ref="model_storage_file" />
<field name="groups" eval="[(4, ref('base.group_user'))]" />
<field name="domain_force">[(1, '=', 1)]</field>
<field name="perm_read" eval="True" />
<field name="perm_write" eval="False" />
<field name="perm_create" eval="False" />
<field name="perm_unlink" eval="False" />
</record>
</odoo>
2 changes: 1 addition & 1 deletion storage_file/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ <h1>Storage File</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:14eff9ac3e34f90b55e98e5f1d8e938390db9e5d7c2f421d370143c3a26f5e1b
!! source digest: sha256:c7ccd99c8428f973c93fbbdde0021b61a5db3077bc2d7722a2f929611f8ac415
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/license-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/storage/tree/18.0/storage_file"><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-18-0/storage-18-0-storage_file"><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=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>External file management depending on Storage Backend module.</p>
Expand Down
2 changes: 2 additions & 0 deletions storage_file/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
from . import test_storage_file
from . import test_swap_backend
from . import test_is_public
from . import test_controller
Loading
Loading