Initial commit: OCA Storage packages (17 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:06 +02:00
commit 7a380f05d3
659 changed files with 41828 additions and 0 deletions

View file

@ -0,0 +1,46 @@
# Storage File
Odoo addon: storage_file
## Installation
```bash
pip install odoo-bringout-oca-storage-storage_file
```
## Dependencies
This addon depends on:
- storage_backend
## Manifest Information
- **Name**: Storage File
- **Version**: 16.0.1.0.1
- **Category**: Storage
- **License**: LGPL-3
- **Installable**: True
## Source
Based on [OCA/storage](https://github.com/OCA/storage) branch 16.0, addon `storage_file`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Storage_file Module - storage_file
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for storage_file. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,17 @@
# Controllers
HTTP routes provided by this module.
```mermaid
sequenceDiagram
participant U as User/Client
participant C as Module Controllers
participant O as ORM/Views
U->>C: HTTP GET/POST (routes)
C->>O: ORM operations, render templates
O-->>U: HTML/JSON/PDF
```
Notes
- See files in controllers/ for route definitions.

View file

@ -0,0 +1,5 @@
# Dependencies
This addon depends on:
- [storage_backend](../../odoo-bringout-oca-storage-storage_backend)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon storage_file or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-storage-storage_file"
# or
uv pip install odoo-bringout-oca-storage-storage_file"
```

View file

@ -0,0 +1,14 @@
# Models
Detected core models and extensions in storage_file.
```mermaid
classDiagram
class storage_file
class ir_actions_report
class storage_backend
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: storage_file. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon storage_file
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,41 @@
# Security
Access control and security definitions in storage_file.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../storage_file/security/ir.model.access.csv)**
- 2 model access rules
## Record Rules
Row-level security rules defined in:
## Security Groups & Configuration
Security groups and permissions defined in:
- **[storage_file.xml](../storage_file/security/storage_file.xml)**
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../storage_file/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
- **[storage_file.xml](../storage_file/security/storage_file.xml)**
- Security groups, categories, and XML-based rules
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon storage_file
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,42 @@
[project]
name = "odoo-bringout-oca-storage-storage_file"
version = "16.0.0"
description = "Storage File - Storage file in storage backend"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-storage-storage_backend>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["storage_file"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,85 @@
============
Storage File
============
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d386a4285cd08b7d08b3efef78ebf6dd22cf97ddca2c8fd8fe2a17f7caae8c3b
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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
: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
:target: https://github.com/OCA/storage/tree/16.0/storage_file
:alt: OCA/storage
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/storage-16-0/storage-16-0-storage_file
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/storage&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
External file management depending on Storage Backend module.
It include these features:
* link to any Odoo model/record
* store metadata like: checksum, mimetype
Use cases (with help of additional modules):
- store pdf (like invoices) on a file server with high SLA
- access attachments with read/write on prod environment and only read only on dev / testing
**Table of contents**
.. contents::
:local:
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/storage/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
`feedback <https://github.com/OCA/storage/issues/new?body=module:%20storage_file%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Akretion
Contributors
~~~~~~~~~~~~
* Sebastien Beau <sebastien.beau@akretion.com>
* Raphaël Reverdy <raphael.reverdy@akretion.com>
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/storage <https://github.com/OCA/storage/tree/16.0/storage_file>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1,2 @@
from . import controllers
from . import models

View file

@ -0,0 +1,26 @@
# Copyright 2017 Akretion (http://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
"name": "Storage File",
"summary": "Storage file in storage backend",
"version": "16.0.1.0.1",
"category": "Storage",
"website": "https://github.com/OCA/storage",
"author": " Akretion, Odoo Community Association (OCA)",
"license": "LGPL-3",
"development_status": "Production/Stable",
"application": False,
"installable": True,
"external_dependencies": {"python": ["python_slugify"]},
"depends": ["storage_backend"],
"data": [
"views/storage_file_view.xml",
"views/storage_backend_view.xml",
"security/ir.model.access.csv",
"security/storage_file.xml",
"data/ir_cron.xml",
"data/storage_backend.xml",
],
}

View file

@ -0,0 +1 @@
from . import main

View file

@ -0,0 +1,20 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http
from odoo.http import request
class StorageFileController(http.Controller):
@http.route(
["/storage.file/<string:slug_name_with_id>"], type="http", auth="public"
)
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
)
return (
request.env["ir.binary"]
._get_image_stream_from(storage_file, "data")
.get_response()
)

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record forcecreate="True" id="ir_cron_clean_storage_file" model="ir.cron">
<field name="name">Clean Storage File</field>
<field eval="True" name="active" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall" />
<field name="model_id" ref="model_storage_file" />
<field name="state">code</field>
<field name="code">model._clean_storage_file()</field>
</record>
</odoo>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="storage_backend.default_storage_backend" model="storage.backend">
<field name="filename_strategy">name_with_id</field>
<field name="served_by">odoo</field>
<field name="is_public" eval="False" />
</record>
</odoo>

View file

@ -0,0 +1,331 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * storage_file
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__active
msgid "Active"
msgstr "Aktivan"
#. 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"
msgstr "Pregled pozadine koristi internu URL"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid "Base URL used for files"
msgstr "Osnovna URL korišten za datoteke"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__base_url
msgid "Base Url"
msgstr "Osnovna URL"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__base_url_for_files
msgid "Base Url For Files"
msgstr "Osnovna URL za datoteke"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__checksum
msgid "Checksum/SHA1"
msgstr "Kontrolni zbroj/SHA1"
#. module: storage_file
#: model:ir.actions.server,name:storage_file.ir_cron_clean_storage_file_ir_actions_server
#: model:ir.cron,cron_name:storage_file.ir_cron_clean_storage_file
msgid "Clean Storage File"
msgstr "Očisti datoteku skladištenja"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__company_id
msgid "Company"
msgstr "Preduzeće"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__create_uid
msgid "Created by"
msgstr "Kreirao"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__create_date
msgid "Created on"
msgstr "Kreirano"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__data
msgid "Data"
msgstr "Podaci"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__data
msgid "Datas"
msgstr "Podaci"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__backend_view_use_internal_url
msgid ""
"Decide if Odoo backend views should use the external URL (usually a CDN) or "
"the internal url with direct access to the storage. This could save you some"
" money if you pay by CDN traffic."
msgstr ""
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__is_public
msgid ""
"Define if every files stored into this backend are public or not. Examples:\n"
"Private: your file/image can not be displayed is the user is not logged (not available on other website);\n"
"Public: your file/image can be displayed if nobody is logged (useful to display files on external websites)"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__display_name
msgid "Display Name"
msgstr "Prikazani naziv"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__extension
msgid "Extension"
msgstr "Ekstenzija"
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__served_by__external
msgid "External"
msgstr "Vanjski"
#. module: storage_file
#: model:ir.actions.act_window,name:storage_file.act_open_storage_file_view
#: model:ir.ui.menu,name:storage_file.menu_storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_form
#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
msgid "File"
msgstr "Datoteka"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__file_size
msgid "File Size"
msgstr "Veličine datoteke"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__file_type
msgid "File Type"
msgstr "Tip datoteke"
#. module: storage_file
#. odoo-python
#: code:addons/storage_file/models/storage_file.py:0
#, python-format
msgid "File can not be updated, remove it and create a new one"
msgstr "Datoteka se ne može ažurirati, uklonite je i kreirajte novu"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__filename_strategy
msgid "Filename Strategy"
msgstr "Strategija naziva datoteka"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__filename
msgid "Filename without extension"
msgstr "Naziv datoteke bez ekstenzije"
#. 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."
msgstr "HTTP URL za direktno učitavanje datoteke iz skladištenja."
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__url
msgid "HTTP accessible path to the file"
msgstr "HTTP dostupna putanja do datoteke"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__human_file_size
msgid "Human File Size"
msgstr "Ljudska veličina datoteke"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__id
msgid "ID"
msgstr "ID"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"If you have changed parameters via server env settings the URL might look "
"outdated."
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__internal_url
msgid "Internal Url"
msgstr "Interna URL"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__is_public
msgid "Is Public"
msgstr "Je javno"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file____last_update
msgid "Last Modified on"
msgstr "Zadnje mijenjano"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__write_uid
msgid "Last Updated by"
msgstr "Zadnji ažurirao"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__write_date
msgid "Last Updated on"
msgstr "Zadnje ažurirano"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__mimetype
msgid "Mime Type"
msgstr "Mime Tip"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__name
msgid "Name"
msgstr "Naziv:"
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__filename_strategy__name_with_id
msgid "Name and ID"
msgstr "Naziv i ID"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__url_include_directory_path
msgid ""
"Normally the directory_path it's for internal usage. If this flag is enabled"
" the path will be used to compute the public URL."
msgstr ""
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__served_by__odoo
msgid "Odoo"
msgstr "Odoo"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid "Recompute base URL for files"
msgstr "Ponovno izračunaj osnovnu URL za datoteke"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__relative_path
msgid "Relative Path"
msgstr "Relativna putanja"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__relative_path
msgid "Relative location for backend"
msgstr "Relativna lokacija za pozadinu"
#. module: storage_file
#: model:ir.model,name:storage_file.model_ir_actions_report
msgid "Report Action"
msgstr "Akcija izvještaja"
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__filename_strategy__hash
msgid "SHA hash"
msgstr "SHA hash"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__served_by
msgid "Served By"
msgstr "Poslužuje"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"Served by Odoo option will use `web.base.url` as the base URL.\n"
" <br/>Make sure this parameter is properly configured and accessible\n"
" from everwhere you want to access the service."
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__slug
msgid "Slug"
msgstr "Slug"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__slug
msgid "Slug-ified name with ID for URL"
msgstr "Slug-ificirani naziv s ID-om za URL"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__backend_id
msgid "Storage"
msgstr "Skladištenje"
#. module: storage_file
#: model:ir.model,name:storage_file.model_storage_backend
msgid "Storage Backend"
msgstr "Pozadina skladištenja"
#. module: storage_file
#: model:ir.model,name:storage_file.model_storage_file
msgid "Storage File"
msgstr "Datoteka skladištenja"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__filename_strategy
msgid ""
"Strategy to build the name of the file to be stored.\n"
"Name and ID: will store the file with its name + its id.\n"
"SHA Hash: will use the hash of the file as filename (same method as the native attachment storage)"
msgstr ""
#. module: storage_file
#. odoo-python
#: code:addons/storage_file/models/storage_file.py:0
#, python-format
msgid ""
"The filename strategy is empty for the backend %s.\n"
"Please configure it"
msgstr ""
#. module: storage_file
#: model:ir.model.constraint,message:storage_file.constraint_storage_file_path_uniq
msgid "The private path must be uniq per backend"
msgstr "Privatna putanja mora biti jedinstvena po pozadini"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__to_delete
msgid "To Delete"
msgstr "Za brisanje"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__url
msgid "Url"
msgstr "Url"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__url_include_directory_path
msgid "Url Include Directory Path"
msgstr "URL uključuje putanju direktorija"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"When served by external service you might have special environment configuration\n"
" for building final files URLs.\n"
" <br/>For performance reasons, the base URL is computed and stored.\n"
" If you change some parameters (eg: in local dev environment or special instances)\n"
" and you still want to see the images you might need to refresh this URL\n"
" to make sure images and/or files are loaded correctly."
msgstr ""

View file

@ -0,0 +1,368 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * storage_file
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-10-29 00:15+0000\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__active
msgid "Active"
msgstr "Activo"
#. 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"
msgstr "Vista del Servidor Usar Url Interna"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid "Base URL used for files"
msgstr "URL base utilizada para los archivos"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__base_url
msgid "Base Url"
msgstr "Url Base"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__base_url_for_files
msgid "Base Url For Files"
msgstr "Url base Para Archivos"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__checksum
msgid "Checksum/SHA1"
msgstr "Verificación de suma/SHA1"
#. module: storage_file
#: model:ir.actions.server,name:storage_file.ir_cron_clean_storage_file_ir_actions_server
#: model:ir.cron,cron_name:storage_file.ir_cron_clean_storage_file
msgid "Clean Storage File"
msgstr "Archivo de almacenamiento limpio"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__company_id
msgid "Company"
msgstr "Companía"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__create_uid
msgid "Created by"
msgstr "Creado por"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__create_date
msgid "Created on"
msgstr "Creado el"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__data
msgid "Data"
msgstr "Datos"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__data
msgid "Datas"
msgstr "Datos"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__backend_view_use_internal_url
msgid ""
"Decide if Odoo backend views should use the external URL (usually a CDN) or "
"the internal url with direct access to the storage. This could save you some"
" money if you pay by CDN traffic."
msgstr ""
"Decida si las vistas del servidor de Odoo deben usar la URL externa ("
"usualmente un CDN) o la URL interna con acceso directo al almacenamiento. "
"Esto podría ahorrarte algo de dinero si pagas por tráfico CDN."
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__is_public
msgid ""
"Define if every files stored into this backend are public or not. Examples:\n"
"Private: your file/image can not be displayed is the user is not logged (not available on other website);\n"
"Public: your file/image can be displayed if nobody is logged (useful to display files on external websites)"
msgstr ""
"Define si todos los archivos almacenados en este servidor son públicos o no. "
"Ejemplos:\n"
"Privado: su archivo/imagen no puede mostrarse si el usuario no está "
"registrado (no disponible en otros sitios web);\n"
"Público: su archivo/imagen puede mostrarse si nadie ha iniciado sesión (útil "
"para mostrar archivos en sitios web externos)"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__display_name
msgid "Display Name"
msgstr "Mostrar Nombre"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__extension
msgid "Extension"
msgstr "Extensión"
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__served_by__external
msgid "External"
msgstr "Externo"
#. module: storage_file
#: model:ir.actions.act_window,name:storage_file.act_open_storage_file_view
#: model:ir.ui.menu,name:storage_file.menu_storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_form
#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
msgid "File"
msgstr "Fichero"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__file_size
msgid "File Size"
msgstr "Tamaño del Archivo"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__file_type
msgid "File Type"
msgstr "Tipo de Archivo"
#. module: storage_file
#. odoo-python
#: code:addons/storage_file/models/storage_file.py:0
#, python-format
msgid "File can not be updated, remove it and create a new one"
msgstr "El archivo no se puede actualizar, elimínelo y cree uno nuevo"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__filename_strategy
msgid "Filename Strategy"
msgstr "Estrategia de Archivos"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__filename
msgid "Filename without extension"
msgstr "Nombre de archivo sin extensión"
#. 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."
msgstr "URL HTTP para cargar el archivo directamente desde el almacenamiento."
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__url
msgid "HTTP accessible path to the file"
msgstr "Ruta de acceso HTTP al archivo"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__human_file_size
msgid "Human File Size"
msgstr "Tamaño del Archivo Humano"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__id
msgid "ID"
msgstr "ID (identificación)"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"If you have changed parameters via server env settings the URL might look "
"outdated."
msgstr ""
"Si ha cambiado los parámetros a través de la configuración del entorno del "
"servidor, la URL puede parecer obsoleta."
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__internal_url
msgid "Internal Url"
msgstr "Url Interna"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__is_public
msgid "Is Public"
msgstr "Es Público"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file____last_update
msgid "Last Modified on"
msgstr "Última Modificación el"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__write_uid
msgid "Last Updated by"
msgstr "Última actualización por"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__write_date
msgid "Last Updated on"
msgstr "Última Actualización el"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__mimetype
msgid "Mime Type"
msgstr "Tipo Mimo"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__name
msgid "Name"
msgstr "Nombre"
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__filename_strategy__name_with_id
msgid "Name and ID"
msgstr "Nombre e ID"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__url_include_directory_path
msgid ""
"Normally the directory_path it's for internal usage. If this flag is enabled"
" the path will be used to compute the public URL."
msgstr ""
"Normalmente el directory_path es para uso interno. Si se activa esta opción, "
"la ruta se utilizará para calcular la URL pública."
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__served_by__odoo
msgid "Odoo"
msgstr "Odoo"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid "Recompute base URL for files"
msgstr "Recalcular la URL base de los archivos"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__relative_path
msgid "Relative Path"
msgstr "Trayectoria Relativa"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__relative_path
msgid "Relative location for backend"
msgstr "Ubicación relativa del servidor"
#. module: storage_file
#: model:ir.model,name:storage_file.model_ir_actions_report
msgid "Report Action"
msgstr "Informar Acción"
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__filename_strategy__hash
msgid "SHA hash"
msgstr "Clave SHA"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__served_by
msgid "Served By"
msgstr "Servido Por"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"Served by Odoo option will use `web.base.url` as the base URL.\n"
" <br/>Make sure this parameter is properly configured and accessible\n"
" from everwhere you want to access the service."
msgstr ""
"La opción servida por Odoo utilizará `web.base.url` como URL base.\n"
" <br/>Asegúrese de que este parámetro esté configurado "
"correctamente y sea accesible\n"
" desde cualquier lugar que desee acceder al servicio."
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__slug
msgid "Slug"
msgstr "Babosa"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__slug
msgid "Slug-ified name with ID for URL"
msgstr "Nombre slug-ificado con ID para URL"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__backend_id
msgid "Storage"
msgstr "Almacenamiento"
#. module: storage_file
#: model:ir.model,name:storage_file.model_storage_backend
msgid "Storage Backend"
msgstr "Servidor de Almacenamiento"
#. module: storage_file
#: model:ir.model,name:storage_file.model_storage_file
msgid "Storage File"
msgstr "Fichero de Almacenamiento"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__filename_strategy
msgid ""
"Strategy to build the name of the file to be stored.\n"
"Name and ID: will store the file with its name + its id.\n"
"SHA Hash: will use the hash of the file as filename (same method as the native attachment storage)"
msgstr ""
"Estrategia para construir el nombre del fichero a almacenar.\n"
"Nombre e ID: almacenará el fichero con su nombre + su id.\n"
"SHA Hash: utilizará el hash del archivo como nombre de archivo (mismo método "
"que el almacenamiento nativo de archivos adjuntos)"
#. module: storage_file
#. odoo-python
#: code:addons/storage_file/models/storage_file.py:0
#, python-format
msgid ""
"The filename strategy is empty for the backend %s.\n"
"Please configure it"
msgstr ""
"La estrategia de nombre de archivo está vacía para el servidor %s.\n"
"Por favor, configúrela"
#. module: storage_file
#: model:ir.model.constraint,message:storage_file.constraint_storage_file_path_uniq
msgid "The private path must be uniq per backend"
msgstr "La ruta privada debe ser única por servidor"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__to_delete
msgid "To Delete"
msgstr "Para Eliminar"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__url
msgid "Url"
msgstr "Url"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__url_include_directory_path
msgid "Url Include Directory Path"
msgstr "Url Incluir Ruta Directorio"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"When served by external service you might have special environment configuration\n"
" for building final files URLs.\n"
" <br/>For performance reasons, the base URL is computed and stored.\n"
" If you change some parameters (eg: in local dev environment or special instances)\n"
" and you still want to see the images you might need to refresh this URL\n"
" to make sure images and/or files are loaded correctly."
msgstr ""
"Cuando el servicio es atendido por un servicio externo, es posible que tenga "
"una configuración de entorno especial.\n"
" para crear URLs de archivos finales.\n"
" <br/>Por motivos de rendimiento, la URL base se calcula "
"y almacena.\n"
" Si cambia algunos parámetros (por ejemplo: en el entorno "
"de desarrollo local o en instancias especiales)\n"
" y aún desea ver las imágenes, es posible que necesite "
"actualizar esta URL\n"
" para asegurarse de que las imágenes y/o archivos se "
"carguen correctamente."

View file

@ -0,0 +1,368 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * storage_file
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-02-11 11:06+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.6.2\n"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__active
msgid "Active"
msgstr "Attivo"
#. 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"
msgstr "Vista backend utilizza URL interno"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid "Base URL used for files"
msgstr "URL base utilizzato per i file"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__base_url
msgid "Base Url"
msgstr "URL base"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__base_url_for_files
msgid "Base Url For Files"
msgstr "URL base per i file"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__checksum
msgid "Checksum/SHA1"
msgstr "Checksum/SHA1"
#. module: storage_file
#: model:ir.actions.server,name:storage_file.ir_cron_clean_storage_file_ir_actions_server
#: model:ir.cron,cron_name:storage_file.ir_cron_clean_storage_file
msgid "Clean Storage File"
msgstr "Pulisci deposito file"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__company_id
msgid "Company"
msgstr "Azienda"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__create_date
msgid "Created on"
msgstr "Creato il"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__data
msgid "Data"
msgstr "Dati"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__data
msgid "Datas"
msgstr "Dati"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__backend_view_use_internal_url
msgid ""
"Decide if Odoo backend views should use the external URL (usually a CDN) or "
"the internal url with direct access to the storage. This could save you some"
" money if you pay by CDN traffic."
msgstr ""
"Decidere se le viste backend Odoo devono usare l'URL esterno (normalmente un "
"CDN) o l'URL interno con accesso diretto al deposito. Questo può far "
"risparmiare denaro se si paga il traffico CDN."
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__is_public
msgid ""
"Define if every files stored into this backend are public or not. Examples:\n"
"Private: your file/image can not be displayed is the user is not logged (not available on other website);\n"
"Public: your file/image can be displayed if nobody is logged (useful to display files on external websites)"
msgstr ""
"Definisce se ogni file archiviato in questo bcackend è pubblico o no. Esempi:"
"\n"
"Privato: il file/immagine non può essere visualizzato se l'utente non ha "
"effettuato l'accesso (non disponibile nel sito web);\n"
"Pubblico: il file/immagine può essere visualizzato se nessuno ha effettuato "
"l'accesso (utile per visualizzare i file in siti web esterni)"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__extension
msgid "Extension"
msgstr "Estensione"
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__served_by__external
msgid "External"
msgstr "Esterno"
#. module: storage_file
#: model:ir.actions.act_window,name:storage_file.act_open_storage_file_view
#: model:ir.ui.menu,name:storage_file.menu_storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_form
#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
msgid "File"
msgstr "File"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__file_size
msgid "File Size"
msgstr "Dimensione file"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__file_type
msgid "File Type"
msgstr "Tipo file"
#. module: storage_file
#. odoo-python
#: code:addons/storage_file/models/storage_file.py:0
#, python-format
msgid "File can not be updated, remove it and create a new one"
msgstr "Il file non può essere caricato, rimuoverlo e crearne uno nuovo"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__filename_strategy
msgid "Filename Strategy"
msgstr "Strategia nome file"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__filename
msgid "Filename without extension"
msgstr "Nome file senza estensione"
#. 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."
msgstr "URL HTTP per caricare il file direttamente dal deposito."
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__url
msgid "HTTP accessible path to the file"
msgstr "Percorso HTTP al file accessibile"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__human_file_size
msgid "Human File Size"
msgstr "Dimensione file umana"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__id
msgid "ID"
msgstr "ID"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"If you have changed parameters via server env settings the URL might look "
"outdated."
msgstr ""
"Se si sono modificati i parametri attraverso le impostazione ambiente server "
"l'URL può risultare vecchio."
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__internal_url
msgid "Internal Url"
msgstr "URL interno"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__is_public
msgid "Is Public"
msgstr "È pubblico"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file____last_update
msgid "Last Modified on"
msgstr "Ultima modifica il"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__mimetype
msgid "Mime Type"
msgstr "Tipo MIME"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__name
msgid "Name"
msgstr "Nome"
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__filename_strategy__name_with_id
msgid "Name and ID"
msgstr "Nome e ID"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__url_include_directory_path
msgid ""
"Normally the directory_path it's for internal usage. If this flag is enabled"
" the path will be used to compute the public URL."
msgstr ""
"Normalmente il directory_path è per uso interno. Se questa opzione è "
"abilitata il percorso verrà utilizzato per calcolare l'URL pubblico."
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__served_by__odoo
msgid "Odoo"
msgstr "Odoo"
#. 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,field_description:storage_file.field_storage_file__relative_path
msgid "Relative Path"
msgstr "Percorso relativo"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__relative_path
msgid "Relative location for backend"
msgstr "Posizione relativa per il backend"
#. module: storage_file
#: model:ir.model,name:storage_file.model_ir_actions_report
msgid "Report Action"
msgstr "Azione resoconto"
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__filename_strategy__hash
msgid "SHA hash"
msgstr "Hash SHA"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__served_by
msgid "Served By"
msgstr "Fornito da"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"Served by Odoo option will use `web.base.url` as the base URL.\n"
" <br/>Make sure this parameter is properly configured and accessible\n"
" from everwhere you want to access the service."
msgstr ""
"Fornito dall'opzione Odoo userà `web.base.url` come URL base.\n"
" <br/>Assicurarsi che questo parametro sia correttamente "
"configurato e accessibile\n"
" da ovunque si voglia accedere a questo servizio."
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__slug
msgid "Slug"
msgstr "Frazione"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__slug
msgid "Slug-ified name with ID for URL"
msgstr "Nome frazionato con ID per URL"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__backend_id
msgid "Storage"
msgstr "Deposito"
#. module: storage_file
#: model:ir.model,name:storage_file.model_storage_backend
msgid "Storage Backend"
msgstr "Backend deposito"
#. module: storage_file
#: model:ir.model,name:storage_file.model_storage_file
msgid "Storage File"
msgstr "File deposito"
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__filename_strategy
msgid ""
"Strategy to build the name of the file to be stored.\n"
"Name and ID: will store the file with its name + its id.\n"
"SHA Hash: will use the hash of the file as filename (same method as the native attachment storage)"
msgstr ""
"Strategia per costruire il nome del file da archiviare.\n"
"Nome e ID: archivierà il file con nome + il suo ID\n"
"Hash SHA: utilizzerà l'hash del file come nome del file (stesso metodo del "
"deposito allegati nativo)"
#. module: storage_file
#. odoo-python
#: code:addons/storage_file/models/storage_file.py:0
#, python-format
msgid ""
"The filename strategy is empty for the backend %s.\n"
"Please configure it"
msgstr ""
"La strategia del nome file è vuota per il backend %s.\n"
"Configurarlo"
#. module: storage_file
#: model:ir.model.constraint,message:storage_file.constraint_storage_file_path_uniq
msgid "The private path must be uniq per backend"
msgstr "Il percorso privato deve essere univoco per backend"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__to_delete
msgid "To Delete"
msgstr "Da cancellare"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__url
msgid "Url"
msgstr "URL"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__url_include_directory_path
msgid "Url Include Directory Path"
msgstr "L'URL include il percorso cartella"
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"When served by external service you might have special environment configuration\n"
" for building final files URLs.\n"
" <br/>For performance reasons, the base URL is computed and stored.\n"
" If you change some parameters (eg: in local dev environment or special instances)\n"
" and you still want to see the images you might need to refresh this URL\n"
" to make sure images and/or files are loaded correctly."
msgstr ""
"Quando fornito da servizio esterno serve avere una configurazione ambiente "
"speciale\n"
" per costruire gli URL finali dei file.\n"
" <br/>Per motivi di prestazione, l'URL base è calcolato e "
"salvato.\n"
" Se si cambiano alcun parametri (es. nell'ambiente "
"sviluppo locale o istanze speciali)\n"
" e si vuole continuare a vedere le immagini, bisogna "
"aggiornare l'URL\n"
" per essere sicuri che immagini e/o file siano caricati "
"correttamente."

View file

@ -0,0 +1,331 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * storage_file
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__active
msgid "Active"
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"
msgstr ""
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid "Base URL used for files"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__base_url
msgid "Base Url"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__base_url_for_files
msgid "Base Url For Files"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__checksum
msgid "Checksum/SHA1"
msgstr ""
#. module: storage_file
#: model:ir.actions.server,name:storage_file.ir_cron_clean_storage_file_ir_actions_server
#: model:ir.cron,cron_name:storage_file.ir_cron_clean_storage_file
msgid "Clean Storage File"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__company_id
msgid "Company"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__create_uid
msgid "Created by"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__create_date
msgid "Created on"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__data
msgid "Data"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__data
msgid "Datas"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__backend_view_use_internal_url
msgid ""
"Decide if Odoo backend views should use the external URL (usually a CDN) or "
"the internal url with direct access to the storage. This could save you some"
" money if you pay by CDN traffic."
msgstr ""
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__is_public
msgid ""
"Define if every files stored into this backend are public or not. Examples:\n"
"Private: your file/image can not be displayed is the user is not logged (not available on other website);\n"
"Public: your file/image can be displayed if nobody is logged (useful to display files on external websites)"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__display_name
msgid "Display Name"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__extension
msgid "Extension"
msgstr ""
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__served_by__external
msgid "External"
msgstr ""
#. module: storage_file
#: model:ir.actions.act_window,name:storage_file.act_open_storage_file_view
#: model:ir.ui.menu,name:storage_file.menu_storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_form
#: model_terms:ir.ui.view,arch_db:storage_file.storage_file_view_search
msgid "File"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__file_size
msgid "File Size"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__file_type
msgid "File Type"
msgstr ""
#. module: storage_file
#. odoo-python
#: code:addons/storage_file/models/storage_file.py:0
#, python-format
msgid "File can not be updated, remove it and create a new one"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__filename_strategy
msgid "Filename Strategy"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__filename
msgid "Filename without extension"
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."
msgstr ""
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__url
msgid "HTTP accessible path to the file"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__human_file_size
msgid "Human File Size"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__id
msgid "ID"
msgstr ""
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"If you have changed parameters via server env settings the URL might look "
"outdated."
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__internal_url
msgid "Internal Url"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__is_public
msgid "Is Public"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file____last_update
msgid "Last Modified on"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__write_uid
msgid "Last Updated by"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__write_date
msgid "Last Updated on"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__mimetype
msgid "Mime Type"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__name
msgid "Name"
msgstr ""
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__filename_strategy__name_with_id
msgid "Name and ID"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__url_include_directory_path
msgid ""
"Normally the directory_path it's for internal usage. If this flag is enabled"
" the path will be used to compute the public URL."
msgstr ""
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__served_by__odoo
msgid "Odoo"
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,field_description:storage_file.field_storage_file__relative_path
msgid "Relative Path"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__relative_path
msgid "Relative location for backend"
msgstr ""
#. module: storage_file
#: model:ir.model,name:storage_file.model_ir_actions_report
msgid "Report Action"
msgstr ""
#. module: storage_file
#: model:ir.model.fields.selection,name:storage_file.selection__storage_backend__filename_strategy__hash
msgid "SHA hash"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__served_by
msgid "Served By"
msgstr ""
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"Served by Odoo option will use `web.base.url` as the base URL.\n"
" <br/>Make sure this parameter is properly configured and accessible\n"
" from everwhere you want to access the service."
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__slug
msgid "Slug"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_file__slug
msgid "Slug-ified name with ID for URL"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__backend_id
msgid "Storage"
msgstr ""
#. module: storage_file
#: model:ir.model,name:storage_file.model_storage_backend
msgid "Storage Backend"
msgstr ""
#. module: storage_file
#: model:ir.model,name:storage_file.model_storage_file
msgid "Storage File"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,help:storage_file.field_storage_backend__filename_strategy
msgid ""
"Strategy to build the name of the file to be stored.\n"
"Name and ID: will store the file with its name + its id.\n"
"SHA Hash: will use the hash of the file as filename (same method as the native attachment storage)"
msgstr ""
#. module: storage_file
#. odoo-python
#: code:addons/storage_file/models/storage_file.py:0
#, python-format
msgid ""
"The filename strategy is empty for the backend %s.\n"
"Please configure it"
msgstr ""
#. module: storage_file
#: model:ir.model.constraint,message:storage_file.constraint_storage_file_path_uniq
msgid "The private path must be uniq per backend"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__to_delete
msgid "To Delete"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_file__url
msgid "Url"
msgstr ""
#. module: storage_file
#: model:ir.model.fields,field_description:storage_file.field_storage_backend__url_include_directory_path
msgid "Url Include Directory Path"
msgstr ""
#. module: storage_file
#: model_terms:ir.ui.view,arch_db:storage_file.storage_backend_view_form
msgid ""
"When served by external service you might have special environment configuration\n"
" for building final files URLs.\n"
" <br/>For performance reasons, the base URL is computed and stored.\n"
" If you change some parameters (eg: in local dev environment or special instances)\n"
" and you still want to see the images you might need to refresh this URL\n"
" to make sure images and/or files are loaded correctly."
msgstr ""

View file

@ -0,0 +1,3 @@
from . import storage_file
from . import storage_backend
from . import ir_actions_report

View file

@ -0,0 +1,14 @@
# Copyright 2021 Camptocamp SA (http://www.camptocamp.com).
# @author Simone Orsi <simahawk@gmail.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import models
class IrActionsReport(models.Model):
_inherit = "ir.actions.report"
def render_qweb_pdf(self, res_ids=None, data=None):
return super(
IrActionsReport, self.with_context(print_report_pdf=True)
).render_qweb_pdf(res_ids=res_ids, data=data)

View file

@ -0,0 +1,167 @@
# Copyright 2017 Akretion (http://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# Copyright 2019 Camptocamp SA (http://www.camptocamp.com).
# @author Simone Orsi <simone.orsi@camptocamp.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import logging
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class StorageBackend(models.Model):
_inherit = "storage.backend"
filename_strategy = fields.Selection(
selection=[("name_with_id", "Name and ID"), ("hash", "SHA hash")],
default="name_with_id",
help=(
"Strategy to build the name of the file to be stored.\n"
"Name and ID: will store the file with its name + its id.\n"
"SHA Hash: will use the hash of the file as filename "
"(same method as the native attachment storage)"
),
)
served_by = fields.Selection(
selection=[("odoo", "Odoo"), ("external", "External")],
required=True,
default="odoo",
)
base_url = fields.Char(default="")
is_public = fields.Boolean(
default=False,
help="Define if every files stored into this backend are "
"public or not. Examples:\n"
"Private: your file/image can not be displayed is the user is "
"not logged (not available on other website);\n"
"Public: your file/image can be displayed if nobody is "
"logged (useful to display files on external websites)",
)
url_include_directory_path = fields.Boolean(
default=False,
help="Normally the directory_path it's for internal usage. "
"If this flag is enabled "
"the path will be used to compute the public URL.",
)
base_url_for_files = fields.Char(compute="_compute_base_url_for_files", store=True)
backend_view_use_internal_url = fields.Boolean(
help="Decide if Odoo backend views should use the external URL (usually a CDN) "
"or the internal url with direct access to the storage. "
"This could save you some money if you pay by CDN traffic."
)
def write(self, vals):
# Ensure storage file URLs are up to date
clear_url_cache = False
url_related_fields = (
"served_by",
"base_url",
"directory_path",
"url_include_directory_path",
)
for fname in url_related_fields:
if fname in vals:
clear_url_cache = True
break
res = super().write(vals)
if clear_url_cache:
self.action_recompute_base_url_for_files()
return res
@property
def _server_env_fields(self):
env_fields = super()._server_env_fields
env_fields.update(
{
"filename_strategy": {},
"served_by": {},
"base_url": {},
"url_include_directory_path": {},
}
)
return env_fields
_default_backend_xid = "storage_backend.default_storage_backend"
@classmethod
def _get_backend_id_from_param(cls, env, param_name, default_fallback=True):
backend_id = None
param = env["ir.config_parameter"].sudo().get_param(param_name)
if param:
if param.isdigit():
backend_id = int(param)
elif "." in param:
backend = env.ref(param, raise_if_not_found=False)
if backend:
backend_id = backend.id
if not backend_id and default_fallback:
backend = env.ref(cls._default_backend_xid, raise_if_not_found=False)
if backend:
backend_id = backend.id
else:
_logger.warn("No backend found, no default fallback found.")
return backend_id
@api.depends(
"served_by",
"base_url",
"directory_path",
"url_include_directory_path",
)
def _compute_base_url_for_files(self):
for record in self:
record.base_url_for_files = record._get_base_url_for_files()
def _get_base_url_for_files(self):
"""Retrieve base URL for files."""
backend = self.sudo()
parts = []
if backend.served_by == "external":
parts = [backend.base_url or ""]
if backend.url_include_directory_path and backend.directory_path:
parts.append(backend.directory_path)
return "/".join(parts)
def action_recompute_base_url_for_files(self):
"""Refresh base URL for files.
Rationale: all the params for computing this URL might come from server env.
When this is the case, the URL - being stored - might be out of date.
This is because changes to server env fields are not detected at startup.
Hence, let's offer an easy way to promptly force this manually when needed.
"""
self._compute_base_url_for_files()
self.env["storage.file"].invalidate_model(["url"])
def _get_base_url_from_param(self):
base_url_param = (
"report.url" if self.env.context.get("print_report_pdf") else "web.base.url"
)
return self.env["ir.config_parameter"].sudo().get_param(base_url_param)
def _get_url_for_file(self, storage_file):
"""Return final full URL for given file."""
backend = self.sudo()
if backend.served_by == "odoo":
parts = [
self._get_base_url_from_param(),
"storage.file",
storage_file.slug,
]
else:
parts = [backend.base_url_for_files or "", storage_file.relative_path or ""]
return "/".join([x.rstrip("/") for x in parts if x])
def _register_hook(self):
super()._register_hook()
backends = self.search([]).filtered(
lambda x: x._get_base_url_for_files() != x.base_url_for_files
)
if not backends:
return
sql = f"SELECT id FROM {self._table} WHERE ID IN %s FOR UPDATE"
self.env.cr.execute(sql, (tuple(backends.ids),), log_exceptions=False)
backends.action_recompute_base_url_for_files()
_logger.info("storage.backend base URL for files refreshed")

View file

@ -0,0 +1,213 @@
# Copyright 2017 Akretion (http://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import base64
import hashlib
import logging
import mimetypes
import os
import re
from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo.tools import human_size
from odoo.tools.translate import _
_logger = logging.getLogger(__name__)
try:
from slugify import slugify
except ImportError: # pragma: no cover
_logger.debug("Cannot `import slugify`.")
REGEX_SLUGIFY = r"[^-a-z0-9_]+"
class StorageFile(models.Model):
_name = "storage.file"
_description = "Storage File"
name = fields.Char(required=True, index=True)
backend_id = fields.Many2one(
"storage.backend", "Storage", index=True, required=True
)
url = fields.Char(compute="_compute_url", help="HTTP accessible path to the file")
internal_url = fields.Char(
compute="_compute_internal_url",
help="HTTP URL to load the file directly from storage.",
)
slug = fields.Char(
compute="_compute_slug", help="Slug-ified name with ID for URL", store=True
)
relative_path = fields.Char(readonly=True, help="Relative location for backend")
file_size = fields.Integer()
human_file_size = fields.Char(compute="_compute_human_file_size", store=True)
checksum = fields.Char("Checksum/SHA1", size=40, index=True, readonly=True)
filename = fields.Char(
"Filename without extension", compute="_compute_extract_filename", store=True
)
extension = fields.Char(compute="_compute_extract_filename", store=True)
mimetype = fields.Char("Mime Type", compute="_compute_extract_filename", store=True)
data = fields.Binary(
help="Datas", inverse="_inverse_data", compute="_compute_data", store=False
)
to_delete = fields.Boolean()
active = fields.Boolean(default=True)
company_id = fields.Many2one(
"res.company", "Company", default=lambda self: self.env.user.company_id.id
)
file_type = fields.Selection([])
_sql_constraints = [
(
"path_uniq",
"unique(relative_path, backend_id)",
"The private path must be uniq per backend",
)
]
def write(self, vals):
if "data" in vals:
for record in self:
if record.data:
raise UserError(
_("File can not be updated, remove it and create a new one")
)
return super(StorageFile, self).write(vals)
@api.depends("file_size")
def _compute_human_file_size(self):
for record in self:
record.human_file_size = human_size(record.file_size)
@api.depends("filename", "extension")
def _compute_slug(self):
for record in self:
record.slug = record._slugify_name_with_id()
def _slugify_name_with_id(self):
return "{}{}".format(
slugify(
"{}-{}".format(self.filename, self.id), regex_pattern=REGEX_SLUGIFY
),
self.extension,
)
def _build_relative_path(self, checksum):
self.ensure_one()
strategy = self.sudo().backend_id.filename_strategy
if not strategy:
raise UserError(
_(
"The filename strategy is empty for the backend %s.\n"
"Please configure it"
)
% self.backend_id.name
)
if strategy == "hash":
return checksum[:2] + "/" + checksum
elif strategy == "name_with_id":
return self.slug
def _prepare_meta_for_file(self):
bin_data = base64.b64decode(self.data)
checksum = hashlib.sha1(bin_data).hexdigest()
relative_path = self._build_relative_path(checksum)
return {
"checksum": checksum,
"file_size": len(bin_data),
"relative_path": relative_path,
}
def _inverse_data(self):
for record in self:
record.write(record._prepare_meta_for_file())
record.backend_id.sudo().add(
record.relative_path,
record.data,
mimetype=record.mimetype,
binary=False,
)
def _compute_data(self):
for rec in self:
if self._context.get("bin_size"):
rec.data = rec.file_size
elif rec.relative_path:
rec.data = rec.backend_id.sudo().get(rec.relative_path, binary=False)
else:
rec.data = None
@api.depends("relative_path", "backend_id")
def _compute_url(self):
for record in self:
record.url = record._get_url()
def _get_url(self):
"""Retrieve file URL based on backend params."""
return self.backend_id._get_url_for_file(self)
@api.depends("slug")
def _compute_internal_url(self):
for record in self:
record.internal_url = record._get_internal_url()
def _get_internal_url(self):
"""Retrieve file URL to load file directly from the storage.
It is recommended to use this for Odoo backend internal usage
to not generate traffic on external services.
"""
return f"/storage.file/{self.slug}"
@api.depends("name")
def _compute_extract_filename(self):
for rec in self:
if rec.name:
rec.filename, rec.extension = os.path.splitext(rec.name)
mime, __ = mimetypes.guess_type(rec.name)
else:
rec.filename = rec.extension = mime = False
rec.mimetype = mime
def unlink(self):
if self._context.get("cleanning_storage_file"):
super(StorageFile, self).unlink()
else:
self.write({"to_delete": True, "active": False})
return True
@api.model
def _clean_storage_file(self):
# we must be sure that all the changes are into the DB since
# we by pass the ORM
self.flush_model()
self._cr.execute(
"""SELECT id
FROM storage_file
WHERE to_delete=True FOR UPDATE"""
)
ids = [x[0] for x in self._cr.fetchall()]
for st_file in self.browse(ids):
st_file.backend_id.sudo().delete(st_file.relative_path)
st_file.with_context(cleanning_storage_file=True).unlink()
# commit is required since the backend could be an external system
# therefore, if the record is deleted on the external system
# we must be sure that the record is also deleted into Odoo
st_file._cr.commit()
@api.model
def get_from_slug_name_with_id(self, slug_name_with_id):
"""
Return a browse record from a string generated by the method
_slugify_name_with_id
:param slug_name_with_id:
:return: a BrowseRecord (could be empty...)
"""
# id is the last group of digit after '-'
_id = re.findall(r"-([0-9]+)", slug_name_with_id)[-1:]
if _id:
_id = int(_id[0])
return self.browse(_id)

View file

@ -0,0 +1,2 @@
* Sebastien Beau <sebastien.beau@akretion.com>
* Raphaël Reverdy <raphael.reverdy@akretion.com>

View file

@ -0,0 +1,9 @@
External file management depending on Storage Backend module.
It include these features:
* link to any Odoo model/record
* store metadata like: checksum, mimetype
Use cases (with help of additional modules):
- store pdf (like invoices) on a file server with high SLA
- access attachments with read/write on prod environment and only read only on dev / testing

View file

@ -0,0 +1,3 @@
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,,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_storage_file_edit storage_file edit model_storage_file base.group_system 1 1 1 1
3 access_storage_file_read_public storage_file public read model_storage_file 1 0 0 0

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018 ACSONE SA/NV
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<odoo>
<!--Security rule to allow public access on storage.file (depending on the storage.backend)-->
<record id="ir_rule_storage_file_public" model="ir.rule">
<field name="name">Storage file public</field>
<field name="model_id" ref="model_storage_file" />
<field name="groups" eval="[(4, ref('base.group_public'))]" />
<field name="domain_force">[('backend_id.is_public', '=', True)]</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,430 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Storage File</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="storage-file">
<h1 class="title">Storage File</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d386a4285cd08b7d08b3efef78ebf6dd22cf97ddca2c8fd8fe2a17f7caae8c3b
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<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/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/storage/tree/16.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-16-0/storage-16-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=16.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>
<p>It include these features:
* link to any Odoo model/record
* store metadata like: checksum, mimetype</p>
<p>Use cases (with help of additional modules):
- store pdf (like invoices) on a file server with high SLA
- access attachments with read/write on prod environment and only read only on dev / testing</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-1">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-2">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-3">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-4">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-5">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-1">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/storage/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/storage/issues/new?body=module:%20storage_file%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-2">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-3">Authors</a></h2>
<ul class="simple">
<li>Akretion</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-4">Contributors</a></h2>
<ul class="simple">
<li>Sebastien Beau &lt;<a class="reference external" href="mailto:sebastien.beau&#64;akretion.com">sebastien.beau&#64;akretion.com</a>&gt;</li>
<li>Raphaël Reverdy &lt;<a class="reference external" href="mailto:raphael.reverdy&#64;akretion.com">raphael.reverdy&#64;akretion.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/storage/tree/16.0/storage_file">OCA/storage</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1 @@
from . import test_storage_file

View file

@ -0,0 +1,281 @@
# Copyright 2017 Akretion (http://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import base64
from unittest import mock
from urllib import parse
from odoo.exceptions import AccessError, UserError
from odoo.addons.component.tests.common import TransactionComponentCase
class StorageFileCase(TransactionComponentCase):
def setUp(self):
super().setUp()
self.backend = self.env.ref("storage_backend.default_storage_backend")
data = b"This is a simple file"
self.filesize = len(data)
self.filedata = base64.b64encode(data)
self.filename = "test of my_file.txt"
def _create_storage_file(self):
return self.env["storage.file"].create(
{
"name": self.filename,
"backend_id": self.backend.id,
"data": self.filedata,
}
)
def test_create_and_read_served_by_odoo(self):
stfile = self._create_storage_file()
self.assertEqual(stfile.data, self.filedata)
self.assertEqual(stfile.mimetype, "text/plain")
self.assertEqual(stfile.extension, ".txt")
self.assertEqual(stfile.filename, "test of my_file")
self.assertEqual(stfile.relative_path, "test-of-my_file-%s.txt" % stfile.id)
url = parse.urlparse(stfile.url)
self.assertEqual(url.path, "/storage.file/test-of-my_file-%s.txt" % stfile.id)
self.assertEqual(stfile.file_size, self.filesize)
def test_get_from_slug_name_with_id(self):
stfile = self._create_storage_file()
stfile2 = self.env["storage.file"].get_from_slug_name_with_id(
"test-of-my_file-%s.txt" % stfile.id
)
self.assertEqual(stfile, stfile2)
# the method parse the given string to find the id. The id is the
# last sequence of digit starting with '-'
stfile2 = self.env["storage.file"].get_from_slug_name_with_id(
"test-999-%s.txt2" % stfile.id
)
self.assertEqual(stfile, stfile2)
stfile2 = self.env["storage.file"].get_from_slug_name_with_id(
"test-999-%s" % stfile.id
)
self.assertEqual(stfile, stfile2)
def test_slug(self):
stfile = self._create_storage_file()
self.assertEqual(
stfile.slug,
"test-of-my_file-{}.txt".format(stfile.id),
)
stfile.name = "Name has changed.png"
self.assertEqual(
stfile.slug,
"name-has-changed-{}.png".format(stfile.id),
)
def test_internal_url(self):
stfile = self._create_storage_file()
self.assertEqual(
stfile.internal_url,
"/storage.file/test-of-my_file-{}.txt".format(stfile.id),
)
stfile.name = "Name has changed.png"
self.assertEqual(
stfile.slug,
"name-has-changed-{}.png".format(stfile.id),
)
self.assertEqual(
stfile.internal_url,
"/storage.file/name-has-changed-{}.png".format(stfile.id),
)
def test_url(self):
stfile = self._create_storage_file()
params = self.env["ir.config_parameter"].sudo()
base_url = params.get_param("web.base.url")
# served by odoo
self.assertEqual(
stfile.url,
"{}/storage.file/test-of-my_file-{}.txt".format(base_url, stfile.id),
)
# served by external
stfile.backend_id.update(
{
"served_by": "external",
"base_url": "https://foo.com",
"directory_path": "baz",
}
)
# path not included
self.assertEqual(
stfile.url, "https://foo.com/test-of-my_file-{}.txt".format(stfile.id)
)
# path included
stfile.backend_id.url_include_directory_path = True
self.assertEqual(
stfile.url, "https://foo.com/baz/test-of-my_file-{}.txt".format(stfile.id)
)
def test_url_for_report(self):
stfile = self._create_storage_file()
params = self.env["ir.config_parameter"].sudo()
params.set_param("report.url", "http://report.url")
# served by odoo
self.assertEqual(
stfile.with_context(print_report_pdf=True).url,
"http://report.url/storage.file/test-of-my_file-{}.txt".format(stfile.id),
)
def test_create_store_with_hash(self):
self.backend.filename_strategy = "hash"
stfile = self._create_storage_file()
self.assertEqual(stfile.data, self.filedata)
self.assertEqual(stfile.mimetype, "text/plain")
self.assertEqual(stfile.extension, ".txt")
self.assertEqual(stfile.filename, "test of my_file")
self.assertEqual(
stfile.relative_path, "13/1322d9ccb3d257095185b205eadc9307aae5dc84"
)
def test_missing_name_strategy(self):
self.backend.filename_strategy = None
with self.assertRaises(UserError):
self._create_storage_file()
def test_create_and_read_served_by_external(self):
self.backend.write(
{"served_by": "external", "base_url": "https://cdn.example.com"}
)
stfile = self._create_storage_file()
self.assertEqual(stfile.data, self.filedata)
self.assertEqual(
stfile.url, "https://cdn.example.com/test-of-my_file-%s.txt" % stfile.id
)
self.assertEqual(stfile.file_size, self.filesize)
def test_read_bin_size(self):
stfile = self._create_storage_file()
self.assertEqual(stfile.with_context(bin_size=True).data, b"21.00 bytes")
def test_cannot_update_data(self):
stfile = self._create_storage_file()
data = base64.b64encode(b"This is different data")
with self.assertRaises(UserError):
stfile.write({"data": data})
# check that the file have been not modified
self.assertEqual(stfile.read()[0]["data"], self.filedata)
def test_unlink(self):
# Do not commit during the test
self.cr.commit = lambda: True
stfile = self._create_storage_file()
backend = stfile.backend_id
relative_path = stfile.relative_path
stfile.unlink()
# Check the the storage file is set to delete
# and the file still exist on the storage
self.assertEqual(stfile.to_delete, True)
self.assertIn(relative_path, backend.list_files())
# Run the method to clean the storage.file
self.env["storage.file"]._clean_storage_file()
# Check that the file is deleted
files = (
self.env["storage.file"]
.with_context(active_test=False)
.search([("id", "=", stfile.id)])
)
self.assertEqual(len(files), 0)
self.assertNotIn(relative_path, backend.list_files())
def test_public_access1(self):
"""
Test the public access (when is_public on the backend).
When checked, the public user should have access to every content
(storage.file).
For this case, we use this public user and try to read a field on
no-public storage.file.
An exception should be raised because the backend is not public
:return: bool
"""
storage_file = self._create_storage_file()
# Ensure it's False (we shouldn't specify a is_public = False on the
# storage.backend creation because False must be the default value)
self.assertFalse(storage_file.backend_id.is_public)
# Public user used on the controller when authentication is 'public'
public_user = self.env.ref("base.public_user")
with self.assertRaises(AccessError):
# BUG OR NOT with_user doesn't invalidate the cache...
# force cache invalidation
self.env.cache.invalidate()
self.env[storage_file._name].with_user(public_user).browse(
storage_file.ids
).name
return True
def test_public_access2(self):
"""
Test the public access (when is_public on the backend).
When checked, the public user should have access to every content
(storage.file).
For this case, we use this public user and try to read a field on
no-public storage.file.
This public user should have access because the backend is public
:return: bool
"""
storage_file = self._create_storage_file()
storage_file.backend_id.write({"is_public": True})
self.assertTrue(storage_file.backend_id.is_public)
# Public user used on the controller when authentication is 'public'
public_user = self.env.ref("base.public_user")
env = self.env(user=public_user)
storage_file_public = env[storage_file._name].browse(storage_file.ids)
self.assertTrue(storage_file_public.name)
return True
def test_public_access3(self):
"""
Test the public access (when is_public on the backend).
When checked, the public user should have access to every content
(storage.file).
For this case, we use the demo user and try to read a field on
no-public storage.file (no exception should be raised)
:return: bool
"""
storage_file = self._create_storage_file()
# Ensure it's False (we shouldn't specify a is_public = False on the
# storage.backend creation because False must be the default value)
self.assertFalse(storage_file.backend_id.is_public)
demo_user = self.env.ref("base.user_demo")
env = self.env(user=demo_user)
storage_file_public = env[storage_file._name].browse(storage_file.ids)
self.assertTrue(storage_file_public.name)
return True
def test_get_backend_from_param(self):
storage_file = self._create_storage_file()
with mock.patch.object(
type(self.env["ir.config_parameter"]), "get_param"
) as mocked:
mocked.return_value = str(storage_file.backend_id.id)
self.assertEqual(
self.env["storage.backend"]._get_backend_id_from_param(
self.env, "foo.baz"
),
storage_file.backend_id.id,
)
with mock.patch.object(
type(self.env["ir.config_parameter"]), "get_param"
) as mocked:
mocked.return_value = "storage_backend.default_storage_backend"
self.assertEqual(
self.env["storage.backend"]._get_backend_id_from_param(
self.env, "foo.baz"
),
storage_file.backend_id.id,
)
def test_empty(self):
# get_url is called on new records
empty = self.env["storage.file"].new({})._get_url()
self.assertEqual(empty, "")

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="storage_backend_view_form" model="ir.ui.view">
<field name="model">storage.backend</field>
<field name="inherit_id" ref="storage_backend.storage_backend_view_form" />
<field name="arch" type="xml">
<field name="backend_type" position="after">
<field name="served_by" />
<div
class="alert alert-info"
role="alert"
colspan="2"
attrs="{'invisible': [('served_by', '!=', 'odoo')]}"
>
Served by Odoo option will use `web.base.url` as the base URL.
<br />Make sure this parameter is properly configured and accessible
from everwhere you want to access the service.
</div>
<field
name="is_public"
attrs="{'invisible': [('served_by', '!=', 'odoo')]}"
/>
<field
name="base_url"
attrs="{'invisible':[('served_by', '!=', 'external')]}"
/>
<field name="filename_strategy" />
<field name="backend_view_use_internal_url" />
</field>
<field name="directory_path" position="after">
<field
name="url_include_directory_path"
attrs="{'invisible': [('directory_path', '=', False)]}"
/>
<field
name="base_url_for_files"
string="Base URL used for files"
attrs="{'invisible':[('served_by', '!=', 'external')]}"
/>
<div
class="alert alert-info"
role="alert"
attrs="{'invisible': [('served_by', '!=', 'external')]}"
colspan="2"
>
When served by external service you might have special environment configuration
for building final files URLs.
<br />For performance reasons, the base URL is computed and stored.
If you change some parameters (eg: in local dev environment or special instances)
and you still want to see the images you might need to refresh this URL
to make sure images and/or files are loaded correctly.
</div>
<button
type="object"
name="action_recompute_base_url_for_files"
string="Recompute base URL for files"
help="If you have changed parameters via server env settings the URL might look outdated."
/>
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="storage_file_view_tree" model="ir.ui.view">
<field name="model">storage.file</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="backend_id" />
<field name="file_size" />
<field name="file_type" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
<record id="storage_file_view_form" model="ir.ui.view">
<field name="model">storage.file</field>
<field name="arch" type="xml">
<form string="File">
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1>
<field name="name" />
</h1>
<group>
<field name="id" invisible="True" />
<field
name="backend_id"
attrs="{'readonly': [('id', '!=', False)]}"
/>
<field name="filename" invisible="True" />
<field
name="data"
attrs="{'readonly': [('id', '!=', False)]}"
filename="filename"
/>
<field name="url" widget="url" />
<field name="human_file_size" />
<field name="checksum" />
<field name="relative_path" />
<field name="file_type" />
<field name="company_id" groups="base.group_multi_company" />
</group>
</div>
</form>
</field>
</record>
<record id="storage_file_view_search" model="ir.ui.view">
<field name="model">storage.file</field>
<field name="arch" type="xml">
<search string="File">
<field name="name" />
<field name="backend_id" />
<field name="url" />
</search>
</field>
</record>
<record model="ir.actions.act_window" id="act_open_storage_file_view">
<field name="name">File</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">storage.file</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="storage_file_view_search" />
<field name="domain">[]</field>
<field name="context">{}</field>
</record>
<record model="ir.actions.act_window.view" id="act_open_storage_file_view_form">
<field name="act_window_id" ref="act_open_storage_file_view" />
<field name="sequence" eval="20" />
<field name="view_mode">form</field>
<field name="view_id" ref="storage_file_view_form" />
</record>
<record model="ir.actions.act_window.view" id="act_open_storage_file_view_tree">
<field name="act_window_id" ref="act_open_storage_file_view" />
<field name="sequence" eval="10" />
<field name="view_mode">tree</field>
<field name="view_id" ref="storage_file_view_tree" />
</record>
<menuitem
id="menu_storage_file"
parent="storage_backend.menu_storage"
sequence="20"
action="act_open_storage_file_view"
/>
</odoo>