diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9571e312cc..3bb82f29c2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,10 +10,8 @@ exclude: | ^fs_storage_ms_drive/| ^image_tag/| ^microsoft_drive_account/| - ^storage_backend/| ^storage_backend_ftp/| ^storage_backend_s3/| - ^storage_backend_sftp/| ^storage_file/| ^storage_image/| ^storage_image_product/| diff --git a/requirements.txt b/requirements.txt index 081b1db7b2..275c9dfb40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ fsspec>=2024.5.0 fsspec>=2025.3.0 fsspec[s3] +paramiko python_slugify diff --git a/storage_backend/README.rst b/storage_backend/README.rst index 381ab35a67..caef9da324 100644 --- a/storage_backend/README.rst +++ b/storage_backend/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + =============== Storage Backend =============== @@ -7,23 +11,23 @@ Storage Backend !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:7b577a9f96f6fa3f26a6fa5603de86b8bca5cc78f82d1ed2948b84766989f458 + !! source digest: sha256:93486c7bc0b8c99e2e3fa71a055f5e4e43bf92c40926732f17bb557116c84b97 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png :target: https://odoo-community.org/page/development-status :alt: Production/Stable -.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstorage-lightgray.png?logo=github - :target: https://github.com/OCA/storage/tree/18.0/storage_backend + :target: https://github.com/OCA/storage/tree/19.0/storage_backend :alt: OCA/storage .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/storage-18-0/storage-18-0-storage_backend + :target: https://translation.odoo-community.org/projects/storage-19-0/storage-19-0-storage_backend :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=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/storage&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -51,7 +55,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -66,15 +70,15 @@ Authors Contributors ------------ -- Sébastien BEAU -- Raphaël Reverdy -- Florian da Costa -- Cédric Pigeon -- Renato Lima -- Benoît Guillot -- Laurent Mignon -- Denis Roussel -- Thien Vo +- Sébastien BEAU +- Raphaël Reverdy +- Florian da Costa +- Cédric Pigeon +- Renato Lima +- Benoît Guillot +- Laurent Mignon +- Denis Roussel +- Thien Vo Other credits ------------- @@ -95,6 +99,6 @@ 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 `_ project on GitHub. +This module is part of the `OCA/storage `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/storage_backend/__manifest__.py b/storage_backend/__manifest__.py index 1c5e76b405..d2cb524c43 100644 --- a/storage_backend/__manifest__.py +++ b/storage_backend/__manifest__.py @@ -5,17 +5,18 @@ { "name": "Storage Backend", "summary": "Implement the concept of Storage with amazon S3, sftp...", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "category": "Storage", "website": "https://github.com/OCA/storage", "author": " Akretion, Odoo Community Association (OCA)", "license": "LGPL-3", "development_status": "Production/Stable", - "installable": False, + "installable": True, "depends": ["base", "component", "server_environment"], "data": [ - "views/backend_storage_view.xml", - "data/data.xml", "security/ir.model.access.csv", + "data/data.xml", + "views/backend_storage_view.xml", + "views/storage_backend_category_view.xml", ], } diff --git a/storage_backend/components/filesystem_adapter.py b/storage_backend/components/filesystem_adapter.py index 8bcbc93936..074b423484 100644 --- a/storage_backend/components/filesystem_adapter.py +++ b/storage_backend/components/filesystem_adapter.py @@ -28,13 +28,13 @@ def _basedir(self): def _fullpath(self, relative_path): """This will build the full path for the file, we force to store the data inside the filestore in the directory 'storage". - Becarefull if you implement your own custom path, end user + Be careful if you implement your own custom path, end user should never be able to write or read unwanted filesystem file""" full_path = super()._fullpath(relative_path) base_dir = self._basedir() full_path = os.path.join(base_dir, full_path) if not is_safe_path(base_dir, full_path): - raise AccessError(self.env._("Access to %s is forbidden") % full_path) + raise AccessError(self.env._("Access to %s is forbidden", full_path)) return full_path def add(self, relative_path, data, **kwargs): diff --git a/storage_backend/i18n/es.po b/storage_backend/i18n/es.po index dd0bbfcc51..d314dff078 100644 --- a/storage_backend/i18n/es.po +++ b/storage_backend/i18n/es.po @@ -32,6 +32,23 @@ msgstr "Tipo de servidor" msgid "Backend Type Env Default" msgstr "Tipo de servidor Ent Por defecto" +#. module: storage_backend +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__backend_ids +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_category_view_form +msgid "Backends" +msgstr "" + +#. module: storage_backend +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend__categ_id +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_view_search +msgid "Category" +msgstr "" + +#. module: storage_backend +#: model:ir.model.fields,help:storage_backend.field_storage_backend__categ_id +msgid "Category to group backends for swapping operations" +msgstr "" + #. module: storage_backend #. odoo-python #: code:addons/storage_backend/models/storage_backend.py:0 @@ -46,14 +63,21 @@ msgstr "¡Conexión de Prueba Exitosa!" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__create_uid +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__create_uid msgid "Created by" msgstr "Creado por" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__create_date +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__create_date msgid "Created on" msgstr "Creado el" +#. module: storage_backend +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__description +msgid "Description" +msgstr "" + #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__directory_path msgid "Directory Path" @@ -66,6 +90,7 @@ msgstr "Ruta del Directorio Ent Predet" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__display_name +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__display_name msgid "Display Name" msgstr "Mostrar Nombre" @@ -87,21 +112,25 @@ msgstr "Tiene Validación" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__id +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__id msgid "ID" msgstr "ID(identificación)" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__write_uid +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__write_uid msgid "Last Updated by" msgstr "Última Actualización por" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__write_date +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__write_date msgid "Last Updated on" msgstr "Última Actualización el" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__name +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__name msgid "Name" msgstr "Nombre" @@ -126,6 +155,15 @@ msgstr "Valores por defecto del Entorno de Servidor" msgid "Storage Backend" msgstr "Servidor de Almacenamiento" +#. module: storage_backend +#: model:ir.actions.act_window,name:storage_backend.act_open_storage_backend_category_view +#: model:ir.model,name:storage_backend.model_storage_backend_category +#: model:ir.ui.menu,name:storage_backend.menu_storage_backend_category +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_category_view_form +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_category_view_search +msgid "Storage Backend Category" +msgstr "" + #. module: storage_backend #: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_view_form msgid "Test connection" diff --git a/storage_backend/i18n/it.po b/storage_backend/i18n/it.po index 857e437968..aeba6e1b83 100644 --- a/storage_backend/i18n/it.po +++ b/storage_backend/i18n/it.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2025-06-03 09:27+0000\n" +"PO-Revision-Date: 2026-07-01 11:46+0000\n" "Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" @@ -14,7 +14,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.10.4\n" +"X-Generator: Weblate 5.15.2\n" #. module: storage_backend #. odoo-python @@ -32,6 +32,23 @@ msgstr "Tipo backend" msgid "Backend Type Env Default" msgstr "Tipo backend ambiente predefinito" +#. module: storage_backend +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__backend_ids +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_category_view_form +msgid "Backends" +msgstr "Backend" + +#. module: storage_backend +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend__categ_id +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_view_search +msgid "Category" +msgstr "Categoria" + +#. module: storage_backend +#: model:ir.model.fields,help:storage_backend.field_storage_backend__categ_id +msgid "Category to group backends for swapping operations" +msgstr "Categoria per raggruppare backend per operazioni di scambio" + #. module: storage_backend #. odoo-python #: code:addons/storage_backend/models/storage_backend.py:0 @@ -46,14 +63,21 @@ msgstr "Test connessione avvenuto con successo!" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__create_uid +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__create_uid msgid "Created by" msgstr "Creato da" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__create_date +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__create_date msgid "Created on" msgstr "Creato il" +#. module: storage_backend +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__description +msgid "Description" +msgstr "Descrizione" + #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__directory_path msgid "Directory Path" @@ -66,6 +90,7 @@ msgstr "Percorso cartella ambiente predefinito" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__display_name +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__display_name msgid "Display Name" msgstr "Nome visualizzato" @@ -87,21 +112,25 @@ msgstr "Ha validazione" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__id +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__id msgid "ID" msgstr "ID" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__write_uid +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__write_uid msgid "Last Updated by" msgstr "Ultimo aggiornamento di" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__write_date +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__write_date msgid "Last Updated on" msgstr "Ultimo aggiornamento il" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__name +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__name msgid "Name" msgstr "Nome" @@ -126,6 +155,15 @@ msgstr "Predefiniti ambiente server" msgid "Storage Backend" msgstr "Backend deposito" +#. module: storage_backend +#: model:ir.actions.act_window,name:storage_backend.act_open_storage_backend_category_view +#: model:ir.model,name:storage_backend.model_storage_backend_category +#: model:ir.ui.menu,name:storage_backend.menu_storage_backend_category +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_category_view_form +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_category_view_search +msgid "Storage Backend Category" +msgstr "Categoria backend deposito" + #. module: storage_backend #: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_view_form msgid "Test connection" diff --git a/storage_backend/i18n/storage_backend.pot b/storage_backend/i18n/storage_backend.pot index 2ad02802f2..65aca79709 100644 --- a/storage_backend/i18n/storage_backend.pot +++ b/storage_backend/i18n/storage_backend.pot @@ -29,6 +29,23 @@ msgstr "" msgid "Backend Type Env Default" msgstr "" +#. module: storage_backend +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__backend_ids +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_category_view_form +msgid "Backends" +msgstr "" + +#. module: storage_backend +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend__categ_id +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_view_search +msgid "Category" +msgstr "" + +#. module: storage_backend +#: model:ir.model.fields,help:storage_backend.field_storage_backend__categ_id +msgid "Category to group backends for swapping operations" +msgstr "" + #. module: storage_backend #. odoo-python #: code:addons/storage_backend/models/storage_backend.py:0 @@ -43,14 +60,21 @@ msgstr "" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__create_uid +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__create_uid msgid "Created by" msgstr "" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__create_date +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__create_date msgid "Created on" msgstr "" +#. module: storage_backend +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__description +msgid "Description" +msgstr "" + #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__directory_path msgid "Directory Path" @@ -63,6 +87,7 @@ msgstr "" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__display_name +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__display_name msgid "Display Name" msgstr "" @@ -84,21 +109,25 @@ msgstr "" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__id +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__id msgid "ID" msgstr "" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__write_uid +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__write_uid msgid "Last Updated by" msgstr "" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__write_date +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__write_date msgid "Last Updated on" msgstr "" #. module: storage_backend #: model:ir.model.fields,field_description:storage_backend.field_storage_backend__name +#: model:ir.model.fields,field_description:storage_backend.field_storage_backend_category__name msgid "Name" msgstr "" @@ -123,6 +152,15 @@ msgstr "" msgid "Storage Backend" msgstr "" +#. module: storage_backend +#: model:ir.actions.act_window,name:storage_backend.act_open_storage_backend_category_view +#: model:ir.model,name:storage_backend.model_storage_backend_category +#: model:ir.ui.menu,name:storage_backend.menu_storage_backend_category +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_category_view_form +#: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_category_view_search +msgid "Storage Backend Category" +msgstr "" + #. module: storage_backend #: model_terms:ir.ui.view,arch_db:storage_backend.storage_backend_view_form msgid "Test connection" diff --git a/storage_backend/models/__init__.py b/storage_backend/models/__init__.py index f45f402268..4a96db5b72 100644 --- a/storage_backend/models/__init__.py +++ b/storage_backend/models/__init__.py @@ -1 +1,2 @@ from . import storage_backend +from . import storage_backend_category diff --git a/storage_backend/models/storage_backend.py b/storage_backend/models/storage_backend.py index 2c4e5af464..bb8541d510 100644 --- a/storage_backend/models/storage_backend.py +++ b/storage_backend/models/storage_backend.py @@ -60,6 +60,12 @@ class StorageBackend(models.Model): _description = "Storage Backend" name = fields.Char(required=True) + categ_id = fields.Many2one( + "storage.backend.category", + string="Category", + ondelete="restrict", + help="Category to group backends for swapping operations", + ) backend_type = fields.Selection( selection=[("filesystem", "Filesystem")], required=True, default="filesystem" ) diff --git a/storage_backend/models/storage_backend_category.py b/storage_backend/models/storage_backend_category.py new file mode 100644 index 0000000000..a4e898435b --- /dev/null +++ b/storage_backend/models/storage_backend_category.py @@ -0,0 +1,14 @@ +# Copyright 2026 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + + +class StorageBackendCategory(models.Model): + _name = "storage.backend.category" + _description = "Storage Backend Category" + _order = "name" + + name = fields.Char(required=True, index=True) + description = fields.Text() + backend_ids = fields.One2many("storage.backend", "categ_id", string="Backends") diff --git a/storage_backend/security/ir.model.access.csv b/storage_backend/security/ir.model.access.csv index dd245d4814..1dbee96e50 100644 --- a/storage_backend/security/ir.model.access.csv +++ b/storage_backend/security/ir.model.access.csv @@ -1,2 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_storage_backend_edit,storage_backend edit,model_storage_backend,base.group_system,1,1,1,1 +access_storage_backend_category_edit,storage_backend_category edit,model_storage_backend_category,base.group_system,1,1,1,1 diff --git a/storage_backend/static/description/index.html b/storage_backend/static/description/index.html index 046d42905f..d50692d792 100644 --- a/storage_backend/static/description/index.html +++ b/storage_backend/static/description/index.html @@ -3,7 +3,7 @@ -Storage Backend +README.rst -
-

Storage Backend

+
+ + +Odoo Community Association + +
+

Storage Backend

-

Production/Stable License: LGPL-3 OCA/storage Translate me on Weblate Try me on Runboat

+

Production/Stable License: LGPL-3 OCA/storage Translate me on Weblate Try me on Runboat

Table of contents

    @@ -386,29 +391,29 @@

    Storage Backend

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Akretion
-

Contributors

+

Contributors

-

Other credits

+

Other credits

The migration of this module from 16.0 to 18.0 was financially supported by Camptocamp.

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -435,10 +440,11 @@

Maintainers

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 project on GitHub.

+

This module is part of the OCA/storage project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
diff --git a/storage_backend/tests/common.py b/storage_backend/tests/common.py index 3f25f6db7e..13f3a24fdf 100644 --- a/storage_backend/tests/common.py +++ b/storage_backend/tests/common.py @@ -75,4 +75,9 @@ def setUpClass(cls): cls.filedata = base64.b64encode(b"This is a simple file") cls.filename = "test_file.txt" cls.case_with_subdirectory = "subdirectory/here" - cls.demo_user = cls.env.ref("base.user_demo") + cls.demo_user = cls.env["res.users"].create( + { + "name": "SB demo user", + "login": "sb_demo_user", + } + ) diff --git a/storage_backend/tests/test_filesystem.py b/storage_backend/tests/test_filesystem.py index a136401b28..d8d7ccf1a0 100644 --- a/storage_backend/tests/test_filesystem.py +++ b/storage_backend/tests/test_filesystem.py @@ -20,8 +20,8 @@ def test_setting_and_getting_data_from_dir(self): self._test_setting_and_getting_data_from_dir() def test_find_files(self): - good_filepaths = ["somepath/file%d.good" % x for x in range(1, 10)] - bad_filepaths = ["somepath/file%d.bad" % x for x in range(1, 10)] + good_filepaths = [f"somepath/file{x}.good" for x in range(1, 10)] + bad_filepaths = [f"somepath/file{x}.bad" for x in range(1, 10)] mocked_filepaths = bad_filepaths + good_filepaths backend = self.backend.sudo() base_dir = backend._get_adapter()._basedir() diff --git a/storage_backend/views/backend_storage_view.xml b/storage_backend/views/backend_storage_view.xml index 5c9735111a..339bf9bf53 100644 --- a/storage_backend/views/backend_storage_view.xml +++ b/storage_backend/views/backend_storage_view.xml @@ -5,6 +5,7 @@ + @@ -30,6 +31,7 @@
+ @@ -42,6 +44,12 @@ + + diff --git a/storage_backend/views/storage_backend_category_view.xml b/storage_backend/views/storage_backend_category_view.xml new file mode 100644 index 0000000000..b85bb63f4e --- /dev/null +++ b/storage_backend/views/storage_backend_category_view.xml @@ -0,0 +1,54 @@ + + + + storage.backend.category + + + + + + + + + storage.backend.category + +
+ + + + + + + + + + + +
+
+
+ + + storage.backend.category + + + + + + + + + Storage Backend Category + ir.actions.act_window + storage.backend.category + list,form + + + + +
diff --git a/storage_backend_sftp/__manifest__.py b/storage_backend_sftp/__manifest__.py index e17dc41f82..cc2b5479a8 100644 --- a/storage_backend_sftp/__manifest__.py +++ b/storage_backend_sftp/__manifest__.py @@ -5,12 +5,12 @@ { "name": "Storage Backend SFTP", "summary": "Implement SFTP Storage", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "category": "Storage", "website": "https://github.com/OCA/storage", "author": " Akretion,Odoo Community Association (OCA)", "license": "LGPL-3", - "installable": False, + "installable": True, "external_dependencies": {"python": ["paramiko"]}, "depends": ["storage_backend"], "data": ["views/backend_storage_view.xml"], diff --git a/storage_backend_sftp/components/sftp_adapter.py b/storage_backend_sftp/components/sftp_adapter.py index f54acd123b..7a530b44bb 100644 --- a/storage_backend_sftp/components/sftp_adapter.py +++ b/storage_backend_sftp/components/sftp_adapter.py @@ -29,12 +29,10 @@ def sftp_mkdirs(client, path, mode=511): def load_ssh_key(ssh_key_buffer): - for pkey_class in ( - paramiko.RSAKey, - paramiko.DSSKey, - paramiko.ECDSAKey, - paramiko.Ed25519Key, - ): + pkey_classes = [paramiko.RSAKey, paramiko.ECDSAKey, paramiko.Ed25519Key] + if hasattr(paramiko, "DSSKey"): + pkey_classes.append(paramiko.DSSKey) + for pkey_class in pkey_classes: try: return pkey_class.from_private_key(ssh_key_buffer) except paramiko.SSHException: @@ -44,16 +42,27 @@ def load_ssh_key(ssh_key_buffer): @contextmanager def sftp(backend): - transport = paramiko.Transport((backend.sftp_server, backend.sftp_port)) - if backend.sftp_auth_method == "pwd": - transport.connect(username=backend.sftp_login, password=backend.sftp_password) - elif backend.sftp_auth_method == "ssh_key": - ssh_key_buffer = StringIO(backend.sftp_ssh_private_key) - private_key = load_ssh_key(ssh_key_buffer) - transport.connect(username=backend.sftp_login, pkey=private_key) - client = paramiko.SFTPClient.from_transport(transport) - yield client - transport.close() + transport = None + client = None + try: + transport = paramiko.Transport((backend.sftp_server, backend.sftp_port)) + if backend.sftp_auth_method == "pwd": + transport.connect( + username=backend.sftp_login, password=backend.sftp_password + ) + elif backend.sftp_auth_method == "ssh_key": + ssh_key_buffer = StringIO(backend.sftp_ssh_private_key) + private_key = load_ssh_key(ssh_key_buffer) + transport.connect(username=backend.sftp_login, pkey=private_key) + client = paramiko.SFTPClient.from_transport(transport) + yield client + finally: + try: + if client: + client.close() + finally: + if transport: + transport.close() class SFTPStorageBackendAdapter(Component): @@ -73,17 +82,14 @@ def add(self, relative_path, data, **kwargs): sftp_mkdirs(client, dirname) else: raise # pragma: no cover - remote_file = client.open(full_path, "w") - remote_file.write(data) - remote_file.close() + with client.open(full_path, "w") as remote_file: + remote_file.write(data) def get(self, relative_path, **kwargs): full_path = self._fullpath(relative_path) with sftp(self.collection) as client: - file_data = client.open(full_path, "r") - data = file_data.read() - file_data.close() - return data + with client.open(full_path, "r") as file_data: + return file_data.read() def list(self, relative_path): full_path = self._fullpath(relative_path) @@ -94,8 +100,7 @@ def list(self, relative_path): if e.errno == errno.ENOENT: # The path do not exist return an empty list return [] - else: - raise # pragma: no cover + raise # pragma: no cover def move_files(self, files, destination_path): _logger.debug("mv %s %s", files, destination_path) diff --git a/storage_backend_sftp/tests/test_sftp.py b/storage_backend_sftp/tests/test_sftp.py index fd05382c07..126b97ee3c 100644 --- a/storage_backend_sftp/tests/test_sftp.py +++ b/storage_backend_sftp/tests/test_sftp.py @@ -4,9 +4,6 @@ # @author Simone Orsi # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -# pylint: disable=missing-manifest-dependency -# disable warning on 'vcr' missing in manifest: this is only a dependency for -# dev/tests import errno import logging @@ -51,13 +48,13 @@ def test_add(self, mocked_paramiko, mocked_mkdirs): # not found exc.errno = errno.ENOENT client.stat.side_effect = exc - fakefile = open("/tmp/fakefile.txt", "w+b") - client.open.return_value = fakefile - self.backend.add("fake/path", b"fake data") - # mkdirs has been called - mocked_mkdirs.assert_called() - # file has been written and closed - self.assertTrue(fakefile.closed) + with open("/tmp/fakefile.txt", "w+b") as fakefile: + client.open.return_value = fakefile + self.backend.add("fake/path", b"fake data") + # mkdirs has been called + mocked_mkdirs.assert_called() + # file has been written and closed + self.assertTrue(fakefile.closed) with open("/tmp/fakefile.txt") as thefile: self.assertEqual(thefile.read(), "fake data") @@ -66,8 +63,9 @@ def test_get(self, mocked_paramiko): client = mocked_paramiko.SFTPClient.from_transport() with open("/tmp/fakefile2.txt", "w+b") as fakefile: fakefile.write(b"filecontent") - client.open.return_value = open("/tmp/fakefile2.txt") - self.assertEqual(self.backend.get("fake/path"), "filecontent") + with open("/tmp/fakefile2.txt") as reading_file: + client.open.return_value = reading_file + self.assertEqual(self.backend.get("fake/path"), "filecontent") @mock.patch(PARAMIKO_PATH) def test_list(self, mocked_paramiko): @@ -84,8 +82,8 @@ def test_list(self, mocked_paramiko): self.assertEqual(self.backend.list_files(), []) def test_find_files(self): - good_filepaths = ["somepath/file%d.good" % x for x in range(1, 10)] - bad_filepaths = ["somepath/file%d.bad" % x for x in range(1, 10)] + good_filepaths = [f"somepath/file{x}.good" for x in range(1, 10)] + bad_filepaths = [f"somepath/file{x}.bad" for x in range(1, 10)] mocked_filepaths = bad_filepaths + good_filepaths backend = self.backend.sudo() expected = good_filepaths[:]