diff --git a/README.md b/README.md index 5501d11f6a..adf7845074 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,10 @@ addon | version | maintainers | summary [fs_storage_backup](fs_storage_backup/) | 16.0.2.0.0 | | Filesystem Storage Backup [fs_storage_backup_environment](fs_storage_backup_environment/) | 16.0.1.0.0 | | Allows to use server environment with fs storage attachment [fs_storage_environment](fs_storage_environment/) | 16.0.1.0.0 | | Allows to use server environment with fs storage -[image_tag](image_tag/) | 16.0.1.0.1 | | Image tag model +[image_tag](image_tag/) | 16.0.2.0.0 | | Image tag model +[image_tag_environment](image_tag_environment/) | 16.0.2.0.0 | | Server environment features for the Image Tag model [storage_backend](storage_backend/) | 16.0.1.1.0 | | Implement the concept of Storage with amazon S3, sftp... +[storage_backend_ftp](storage_backend_ftp/) | 16.0.1.0.0 | | Implement FTP Storage [storage_backend_sftp](storage_backend_sftp/) | 16.0.1.0.2 | | Implement SFTP Storage [storage_file](storage_file/) | 16.0.1.0.1 | | Storage file in storage backend diff --git a/image_tag/README.rst b/image_tag/README.rst index 81ab69688d..3be438dc6d 100644 --- a/image_tag/README.rst +++ b/image_tag/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 + ========= Image Tag ========= @@ -7,13 +11,13 @@ Image Tag !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:05203837238360e0c011f8ed825bbf70e20f56a5157f18585a37353e9a148a86 + !! source digest: sha256:9a9aa1210e00d5861a492c284df7dd7c5b975689b83cdaef8f98a15768340cf1 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstorage-lightgray.png?logo=github diff --git a/image_tag/__manifest__.py b/image_tag/__manifest__.py index 0bbe310d2d..e71666e992 100644 --- a/image_tag/__manifest__.py +++ b/image_tag/__manifest__.py @@ -5,11 +5,11 @@ "name": "Image Tag", "summary": """ Image tag model""", - "version": "16.0.1.0.1", + "version": "16.0.2.0.0", "license": "AGPL-3", "author": "ACSONE SA/NV,Akretion,Odoo Community Association (OCA)", "website": "https://github.com/OCA/storage", - "depends": ["server_environment"], + "depends": ["base"], "data": [ "security/res_groups.xml", "security/image_tag.xml", diff --git a/image_tag/i18n/es.po b/image_tag/i18n/es.po index 36459bc555..026a3c19bd 100644 --- a/image_tag/i18n/es.po +++ b/image_tag/i18n/es.po @@ -73,18 +73,9 @@ msgstr "Última Actualización el" msgid "Name" msgstr "Nombre" -#. module: image_tag -#: model:ir.model.fields,field_description:image_tag.field_image_tag__server_env_defaults -msgid "Server Env Defaults" -msgstr "" - -#. module: image_tag -#: model:ir.model.fields,field_description:image_tag.field_image_tag__tech_name -msgid "Tech Name" -msgstr "Nombre Técnico" +#~ msgid "Tech Name" +#~ msgstr "Nombre Técnico" -#. module: image_tag -#: model:ir.model.fields,help:image_tag.field_image_tag__tech_name -msgid "Unique name for technical purposes. Eg: server env keys." -msgstr "" -"Nombre único con fines técnicos. Por ejemplo: claves de ent del servidor." +#~ msgid "Unique name for technical purposes. Eg: server env keys." +#~ msgstr "" +#~ "Nombre único con fines técnicos. Por ejemplo: claves de ent del servidor." diff --git a/image_tag/i18n/image_tag.pot b/image_tag/i18n/image_tag.pot index 9229bba009..8d9040591c 100644 --- a/image_tag/i18n/image_tag.pot +++ b/image_tag/i18n/image_tag.pot @@ -69,18 +69,3 @@ msgstr "" #: model:ir.model.fields,field_description:image_tag.field_image_tag__name msgid "Name" msgstr "" - -#. module: image_tag -#: model:ir.model.fields,field_description:image_tag.field_image_tag__server_env_defaults -msgid "Server Env Defaults" -msgstr "" - -#. module: image_tag -#: model:ir.model.fields,field_description:image_tag.field_image_tag__tech_name -msgid "Tech Name" -msgstr "" - -#. module: image_tag -#: model:ir.model.fields,help:image_tag.field_image_tag__tech_name -msgid "Unique name for technical purposes. Eg: server env keys." -msgstr "" diff --git a/image_tag/i18n/it.po b/image_tag/i18n/it.po index 3d4d2f19d0..b61b9cf738 100644 --- a/image_tag/i18n/it.po +++ b/image_tag/i18n/it.po @@ -73,17 +73,11 @@ msgstr "Ultimo aggiornamento il" msgid "Name" msgstr "Nome" -#. module: image_tag -#: model:ir.model.fields,field_description:image_tag.field_image_tag__server_env_defaults -msgid "Server Env Defaults" -msgstr "Predefiniti ambiente server" +#~ msgid "Server Env Defaults" +#~ msgstr "Predefiniti ambiente server" -#. module: image_tag -#: model:ir.model.fields,field_description:image_tag.field_image_tag__tech_name -msgid "Tech Name" -msgstr "Nome tecnico" +#~ msgid "Tech Name" +#~ msgstr "Nome tecnico" -#. module: image_tag -#: model:ir.model.fields,help:image_tag.field_image_tag__tech_name -msgid "Unique name for technical purposes. Eg: server env keys." -msgstr "Nome univoco per motivi tecnici. Es: chiavi server ambiente." +#~ msgid "Unique name for technical purposes. Eg: server env keys." +#~ msgstr "Nome univoco per motivi tecnici. Es: chiavi server ambiente." diff --git a/image_tag/models/image_tag.py b/image_tag/models/image_tag.py index 86f255873d..847d4e1544 100644 --- a/image_tag/models/image_tag.py +++ b/image_tag/models/image_tag.py @@ -8,7 +8,6 @@ class ImageTag(models.Model): _name = "image.tag" - _inherit = ["server.env.techname.mixin"] _description = "Image Tag" @api.model diff --git a/image_tag/static/description/index.html b/image_tag/static/description/index.html index a37630a2ff..3003169efd 100644 --- a/image_tag/static/description/index.html +++ b/image_tag/static/description/index.html @@ -1,18 +1,18 @@ - -Image Tag +README.rst -
-

Image Tag

+
+ + +Odoo Community Association + +
+

Image Tag

-

Beta License: AGPL-3 OCA/storage Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/storage Translate me on Weblate Try me on Runboat

This addon provide only one basic model used to define image’s tags. These tags are used by other addons to enrich the image’s information of an image linked to an other model. The fs_product_multi_image addon use this model.

@@ -387,12 +392,12 @@

Image Tag

-

Usage

+

Usage

To manage the list of available tags, you must have the Image Tag Manager role.

-

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 @@ -400,24 +405,26 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • ACSONE SA/NV
  • Akretion
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

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.

@@ -426,5 +433,6 @@

Maintainers

+
diff --git a/image_tag/upgrades/16.0.1.0.2/post-update.py b/image_tag/upgrades/16.0.1.0.2/post-update.py new file mode 100644 index 0000000000..574c6795b0 --- /dev/null +++ b/image_tag/upgrades/16.0.1.0.2/post-update.py @@ -0,0 +1,13 @@ +# Copyright 2026 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + module = env["ir.module.module"].search( + [("name", "=", "image_tag_environment"), ("state", "=", "uninstalled")] + ) + if module: + module.button_install() diff --git a/image_tag/views/image_tag.xml b/image_tag/views/image_tag.xml index 6ecda60972..18411f7c4b 100644 --- a/image_tag/views/image_tag.xml +++ b/image_tag/views/image_tag.xml @@ -5,7 +5,6 @@ - @@ -15,7 +14,6 @@ - diff --git a/image_tag_environment/README.rst b/image_tag_environment/README.rst new file mode 100644 index 0000000000..fc8b7541df --- /dev/null +++ b/image_tag_environment/README.rst @@ -0,0 +1,87 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +============================== +Image Tag - Server Environment +============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:20e304c07b50e37319543a302422d4bae8d9b6e234f6c8138b12e938a68c2ab9 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstorage-lightgray.png?logo=github + :target: https://github.com/OCA/storage/tree/16.0/image_tag_environment + :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-image_tag_environment + :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| + +Glue module to make Server Environment features available for the Image Tag addon. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Specify technical name during the creation of an image tag. + +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ACSONE SA/NV +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Laurent Mignon +* Maksym Yankin + +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 `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/image_tag_environment/__init__.py b/image_tag_environment/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/image_tag_environment/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/image_tag_environment/__manifest__.py b/image_tag_environment/__manifest__.py new file mode 100644 index 0000000000..1ed162d063 --- /dev/null +++ b/image_tag_environment/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2023 ACSONE SA/NV +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + "name": "Image Tag - Server Environment", + "summary": "Server environment features for the Image Tag model", + "version": "16.0.2.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Akretion,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/storage", + "depends": ["image_tag", "server_environment"], + "data": [ + "views/image_tag.xml", + ], + "auto_install": True, +} diff --git a/image_tag_environment/i18n/es.po b/image_tag_environment/i18n/es.po new file mode 100644 index 0000000000..4c41eb35cd --- /dev/null +++ b/image_tag_environment/i18n/es.po @@ -0,0 +1,68 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * image_tag +# +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 \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: image_tag_environment +#: model:ir.model,name:image_tag_environment.model_image_tag +msgid "Image Tag" +msgstr "Etiqueta de Imagen" + +#. module: image_tag_environment +#: model:ir.model.fields,field_description:image_tag_environment.field_image_tag__server_env_defaults +msgid "Server Env Defaults" +msgstr "" + +#. module: image_tag_environment +#: model:ir.model.fields,field_description:image_tag_environment.field_image_tag__tech_name +msgid "Tech Name" +msgstr "Nombre Técnico" + +#. module: image_tag_environment +#: model:ir.model.fields,help:image_tag_environment.field_image_tag__tech_name +msgid "Unique name for technical purposes. Eg: server env keys." +msgstr "" +"Nombre único con fines técnicos. Por ejemplo: claves de ent del servidor." + +#~ msgid "Apply On" +#~ msgstr "Aplicar En" + +#~ msgid "Created by" +#~ msgstr "Creado por" + +#~ msgid "Created on" +#~ msgstr "Creado el" + +#~ msgid "Display Name" +#~ msgstr "Mostrar Nombre" + +#~ msgid "ID" +#~ msgstr "ID (identificación)" + +#~ msgid "Image Tag Manager" +#~ msgstr "Gestor de Etiquetas de Imagen" + +#~ msgid "Last Modified on" +#~ msgstr "Última Modifiación el" + +#~ msgid "Last Updated by" +#~ msgstr "Actualizado por Última vez por" + +#~ msgid "Last Updated on" +#~ msgstr "Última Actualización el" + +#~ msgid "Name" +#~ msgstr "Nombre" diff --git a/image_tag_environment/i18n/image_tag.pot b/image_tag_environment/i18n/image_tag.pot new file mode 100644 index 0000000000..9229bba009 --- /dev/null +++ b/image_tag_environment/i18n/image_tag.pot @@ -0,0 +1,86 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * image_tag +# +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: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag__apply_on +msgid "Apply On" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag__create_uid +msgid "Created by" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag__create_date +msgid "Created on" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag__display_name +msgid "Display Name" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag__id +msgid "ID" +msgstr "" + +#. module: image_tag +#: model:ir.actions.act_window,name:image_tag.act_open_image_tag_view +#: model:ir.model,name:image_tag.model_image_tag +#: model_terms:ir.ui.view,arch_db:image_tag.image_tag_view_search +msgid "Image Tag" +msgstr "" + +#. module: image_tag +#: model:res.groups,name:image_tag.group_image_tag_manager +msgid "Image Tag Manager" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag____last_update +msgid "Last Modified on" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag__write_date +msgid "Last Updated on" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag__name +msgid "Name" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag__server_env_defaults +msgid "Server Env Defaults" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,field_description:image_tag.field_image_tag__tech_name +msgid "Tech Name" +msgstr "" + +#. module: image_tag +#: model:ir.model.fields,help:image_tag.field_image_tag__tech_name +msgid "Unique name for technical purposes. Eg: server env keys." +msgstr "" diff --git a/image_tag_environment/i18n/image_tag_environment.pot b/image_tag_environment/i18n/image_tag_environment.pot new file mode 100644 index 0000000000..fad2502af0 --- /dev/null +++ b/image_tag_environment/i18n/image_tag_environment.pot @@ -0,0 +1,34 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * image_tag_environment +# +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: image_tag_environment +#: model:ir.model,name:image_tag_environment.model_image_tag +msgid "Image Tag" +msgstr "" + +#. module: image_tag_environment +#: model:ir.model.fields,field_description:image_tag_environment.field_image_tag__server_env_defaults +msgid "Server Env Defaults" +msgstr "" + +#. module: image_tag_environment +#: model:ir.model.fields,field_description:image_tag_environment.field_image_tag__tech_name +msgid "Tech Name" +msgstr "" + +#. module: image_tag_environment +#: model:ir.model.fields,help:image_tag_environment.field_image_tag__tech_name +msgid "Unique name for technical purposes. Eg: server env keys." +msgstr "" diff --git a/image_tag_environment/i18n/it.po b/image_tag_environment/i18n/it.po new file mode 100644 index 0000000000..76dcaea580 --- /dev/null +++ b/image_tag_environment/i18n/it.po @@ -0,0 +1,67 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * image_tag +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-07-23 11:25+0000\n" +"Last-Translator: mymage \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.10.4\n" + +#. module: image_tag_environment +#: model:ir.model,name:image_tag_environment.model_image_tag +msgid "Image Tag" +msgstr "Etichetta immagine" + +#. module: image_tag_environment +#: model:ir.model.fields,field_description:image_tag_environment.field_image_tag__server_env_defaults +msgid "Server Env Defaults" +msgstr "Predefiniti ambiente server" + +#. module: image_tag_environment +#: model:ir.model.fields,field_description:image_tag_environment.field_image_tag__tech_name +msgid "Tech Name" +msgstr "Nome tecnico" + +#. module: image_tag_environment +#: model:ir.model.fields,help:image_tag_environment.field_image_tag__tech_name +msgid "Unique name for technical purposes. Eg: server env keys." +msgstr "Nome univoco per motivi tecnici. Es: chiavi server ambiente." + +#~ msgid "Apply On" +#~ msgstr "Applica a" + +#~ msgid "Created by" +#~ msgstr "Creato da" + +#~ msgid "Created on" +#~ msgstr "Creato il" + +#~ msgid "Display Name" +#~ msgstr "Nome visualizzato" + +#~ msgid "ID" +#~ msgstr "ID" + +#~ msgid "Image Tag Manager" +#~ msgstr "Gestore etichetta immagine" + +#~ msgid "Last Modified on" +#~ msgstr "Ultima modifica il" + +#~ msgid "Last Updated by" +#~ msgstr "Ultimo aggiornamento di" + +#~ msgid "Last Updated on" +#~ msgstr "Ultimo aggiornamento il" + +#~ msgid "Name" +#~ msgstr "Nome" diff --git a/image_tag_environment/models/__init__.py b/image_tag_environment/models/__init__.py new file mode 100644 index 0000000000..888490ab30 --- /dev/null +++ b/image_tag_environment/models/__init__.py @@ -0,0 +1 @@ +from . import image_tag diff --git a/image_tag_environment/models/image_tag.py b/image_tag_environment/models/image_tag.py new file mode 100644 index 0000000000..2c50349ff8 --- /dev/null +++ b/image_tag_environment/models/image_tag.py @@ -0,0 +1,11 @@ +# Copyright 2023 ACSONE SA/NV +# Copyright 2018 Akretion (http://www.akretion.com). +# @author Raphaël Reverdy +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import models + + +class ImageTag(models.Model): + _name = "image.tag" + _inherit = ["image.tag", "server.env.techname.mixin"] diff --git a/image_tag_environment/readme/CONTRIBUTORS.rst b/image_tag_environment/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..0f79cbc864 --- /dev/null +++ b/image_tag_environment/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Laurent Mignon +* Maksym Yankin diff --git a/image_tag_environment/readme/DESCRIPTION.rst b/image_tag_environment/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..4ddadc72f6 --- /dev/null +++ b/image_tag_environment/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Glue module to make Server Environment features available for the Image Tag addon. diff --git a/image_tag_environment/readme/USAGE.rst b/image_tag_environment/readme/USAGE.rst new file mode 100644 index 0000000000..b57527eec8 --- /dev/null +++ b/image_tag_environment/readme/USAGE.rst @@ -0,0 +1 @@ +Specify technical name during the creation of an image tag. diff --git a/image_tag_environment/static/description/icon.png b/image_tag_environment/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/image_tag_environment/static/description/icon.png differ diff --git a/image_tag_environment/static/description/index.html b/image_tag_environment/static/description/index.html new file mode 100644 index 0000000000..5b9b78aaaa --- /dev/null +++ b/image_tag_environment/static/description/index.html @@ -0,0 +1,436 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Image Tag - Server Environment

+ +

Beta License: AGPL-3 OCA/storage Translate me on Weblate Try me on Runboat

+

Glue module to make Server Environment features available for the Image Tag addon.

+

Table of contents

+ +
+

Usage

+

Specify technical name during the creation of an image tag.

+
+
+

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.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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.

+

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

+
+
+
+
+ + diff --git a/image_tag_environment/views/image_tag.xml b/image_tag_environment/views/image_tag.xml new file mode 100644 index 0000000000..b1409ab2b0 --- /dev/null +++ b/image_tag_environment/views/image_tag.xml @@ -0,0 +1,21 @@ + + + + image.tag + + + + + + + + + image.tag + + + + + + + + diff --git a/requirements.txt b/requirements.txt index 275c9dfb40..1ec18d7bcc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ fsspec>=2024.5.0 fsspec>=2025.3.0 fsspec[s3] paramiko +pyftpdlib python_slugify diff --git a/setup/_metapackage/VERSION.txt b/setup/_metapackage/VERSION.txt index f2eb63f0a9..e3e99b0c1a 100644 --- a/setup/_metapackage/VERSION.txt +++ b/setup/_metapackage/VERSION.txt @@ -1 +1 @@ -16.0.20260608.0 \ No newline at end of file +16.0.20260820.1 \ No newline at end of file diff --git a/setup/_metapackage/setup.py b/setup/_metapackage/setup.py index e7a0f2cba5..312f6ce903 100644 --- a/setup/_metapackage/setup.py +++ b/setup/_metapackage/setup.py @@ -27,7 +27,9 @@ 'odoo-addon-fs_storage_backup_environment>=16.0dev,<16.1dev', 'odoo-addon-fs_storage_environment>=16.0dev,<16.1dev', 'odoo-addon-image_tag>=16.0dev,<16.1dev', + 'odoo-addon-image_tag_environment>=16.0dev,<16.1dev', 'odoo-addon-storage_backend>=16.0dev,<16.1dev', + 'odoo-addon-storage_backend_ftp>=16.0dev,<16.1dev', 'odoo-addon-storage_backend_sftp>=16.0dev,<16.1dev', 'odoo-addon-storage_file>=16.0dev,<16.1dev', ], diff --git a/setup/image_tag_environment/odoo/addons/image_tag_environment b/setup/image_tag_environment/odoo/addons/image_tag_environment new file mode 120000 index 0000000000..fdb0826de9 --- /dev/null +++ b/setup/image_tag_environment/odoo/addons/image_tag_environment @@ -0,0 +1 @@ +../../../../image_tag_environment \ No newline at end of file diff --git a/setup/image_tag_environment/setup.py b/setup/image_tag_environment/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/image_tag_environment/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/storage_backend_ftp/odoo/addons/storage_backend_ftp b/setup/storage_backend_ftp/odoo/addons/storage_backend_ftp new file mode 120000 index 0000000000..18973453c2 --- /dev/null +++ b/setup/storage_backend_ftp/odoo/addons/storage_backend_ftp @@ -0,0 +1 @@ +../../../../storage_backend_ftp \ No newline at end of file diff --git a/setup/storage_backend_ftp/setup.py b/setup/storage_backend_ftp/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/storage_backend_ftp/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/storage_backend_ftp/README.rst b/storage_backend_ftp/README.rst new file mode 100644 index 0000000000..6e53cdb747 --- /dev/null +++ b/storage_backend_ftp/README.rst @@ -0,0 +1,81 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +=================== +Storage Backend FTP +=================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:56d23d879b05c109e87056403e4153adf478bf0ac3270e865cf1d248cfc6f3c5 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |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/16.0/storage_backend_ftp + :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_backend_ftp + :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| + +Add FTP as storage backend + +**Table of contents** + +.. contents:: + :local: + +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Acsone SA/NV + +Contributors +~~~~~~~~~~~~ + +* François Honoré +* Lois Rilo + +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 `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/storage_backend_ftp/__init__.py b/storage_backend_ftp/__init__.py new file mode 100644 index 0000000000..0f00a6730d --- /dev/null +++ b/storage_backend_ftp/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import components diff --git a/storage_backend_ftp/__manifest__.py b/storage_backend_ftp/__manifest__.py new file mode 100644 index 0000000000..786f36a0c3 --- /dev/null +++ b/storage_backend_ftp/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2021 ACSONE SA/NV () +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +{ + "name": "Storage Backend FTP", + "summary": "Implement FTP Storage", + "version": "16.0.1.0.0", + "category": "Storage", + "website": "https://github.com/OCA/storage", + "author": " Acsone SA/NV,Odoo Community Association (OCA)", + "license": "LGPL-3", + "external_dependencies": {"python": ["pyftpdlib"]}, + "depends": ["storage_backend"], + "data": ["views/backend_storage_view.xml"], +} diff --git a/storage_backend_ftp/components/__init__.py b/storage_backend_ftp/components/__init__.py new file mode 100644 index 0000000000..72dbc3a308 --- /dev/null +++ b/storage_backend_ftp/components/__init__.py @@ -0,0 +1 @@ +from . import ftp_adapter diff --git a/storage_backend_ftp/components/ftp_adapter.py b/storage_backend_ftp/components/ftp_adapter.py new file mode 100644 index 0000000000..6f2d30a72b --- /dev/null +++ b/storage_backend_ftp/components/ftp_adapter.py @@ -0,0 +1,168 @@ +# Copyright 2021 ACSONE SA/NV () +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +import errno +import io +import logging +import os +import ssl +from contextlib import contextmanager +from io import BytesIO + +from odoo.exceptions import UserError + +from odoo.addons.component.core import Component + +_logger = logging.getLogger(__name__) + +try: + import ftplib +except ImportError as err: # pragma: no cover + _logger.debug(err) + +FTP_SECURITY_TO_PROTOCOL = { + "tls": ssl.PROTOCOL_TLS, + "tlsv1": ssl.PROTOCOL_TLSv1, + "tlsv1_1": ssl.PROTOCOL_TLSv1_1, + "tlsv1_2": ssl.PROTOCOL_TLSv1_2, + "sslv2": "sslv2 has been deprecated due to security issues", + "sslv23": ssl.PROTOCOL_SSLv23, + "sslv3": "sslv3 has been deprecated due to security issues", +} + + +def ftp_mkdirs(client, path): + try: + client.mkd(path) + except IOError as e: + if e.errno == errno.ENOENT and path: + ftp_mkdirs(client, os.path.dirname(path)) + client.mkd(path) + else: + raise # pragma: no cover + + +class ImplicitFTPTLS(ftplib.FTP_TLS): + """FTP_TLS subclass that automatically wraps sockets in SSL to support implicit FTPS.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._sock = None + + @property + def sock(self): + """Return the socket.""" + return self._sock + + @sock.setter + def sock(self, value): + """When modifying the socket, ensure that it is ssl wrapped.""" + if value is not None and not isinstance(value, ssl.SSLSocket): + value = self.context.wrap_socket(value) + self._sock = value + + +@contextmanager +def ftp(backend): + security = None + prot_p = False + if backend.ftp_encryption in ["ftp", "tls", "tls_explicit"]: + if backend.ftp_encryption == "ftp": + _ftp = ftplib.FTP(timeout=30) + elif backend.ftp_encryption == "tls": + _ftp = ImplicitFTPTLS() + # Due to a bug into between ftplib and ssl, this part (about ssl) might not work! + # https://bugs.python.org/issue31727 + security = FTP_SECURITY_TO_PROTOCOL.get(backend.ftp_security, None) + prot_p = True + if isinstance(security, str): + raise UserError(security) + elif backend.ftp_encryption == "tls_explicit": + _ftp = ftplib.FTP_TLS() + prot_p = True + with _ftp as client: + if security: + client.ssl_version = security + client.connect(host=backend.ftp_server, port=backend.ftp_port) + client.login(backend.ftp_login, backend.ftp_password) + if prot_p: + client.prot_p() + if backend.ftp_passive: + client.set_pasv(True) + yield client + + +class FTPStorageBackendAdapter(Component): + _name = "ftp.adapter" + _inherit = "base.storage.adapter" + _usage = "ftp" + + def add(self, relative_path, data, **kwargs): + with ftp(self.collection) as client: + full_path = self._fullpath(relative_path) + dirname = os.path.dirname(full_path) + if dirname: + try: + client.cwd(dirname) + except IOError as e: + if e.errno == errno.ENOENT: + ftp_mkdirs(client, dirname) + else: + raise # pragma: no cover + with io.BytesIO(data) as tmp_file: + try: + client.storbinary("STOR " + full_path, tmp_file) + except ftplib.Error as e: + raise ValueError(repr(e)) from e + except OSError as e: + raise ValueError(repr(e)) from e + + def get(self, relative_path, **kwargs): + full_path = self._fullpath(relative_path) + with ftp(self.collection) as client, BytesIO() as buff: + try: + client.retrbinary("RETR " + full_path, buff.write) + data = buff.getvalue() + except ftplib.Error as e: + raise FileNotFoundError(repr(e)) from e + return data + + def list(self, relative_path): + full_path = self._fullpath(relative_path) + with ftp(self.collection) as client: + try: + return client.nlst(full_path) + except IOError as e: + if e.errno == errno.ENOENT: + # The path do not exist return an empty list + return [] + else: + raise # pragma: no cover + + def move_files(self, files, destination_path): + _logger.debug("mv %s %s", files, destination_path) + fp = self._fullpath + with ftp(self.collection) as client: + for ftp_file in files: + dest_file_path = os.path.join( + destination_path, os.path.basename(ftp_file) + ) + # Remove existing file at the destination path (an error is raised + # otherwise) + result = [] + try: + result = client.nlst(dest_file_path) + except ftplib.Error: + _logger.debug("destination %s is free", dest_file_path) + if result: + client.delete(dest_file_path) + # Move the file using absolute filepaths + client.rename(fp(ftp_file), fp(dest_file_path)) + + def delete(self, relative_path): + full_path = self._fullpath(relative_path) + with ftp(self.collection) as client: + return client.delete(full_path) + + def validate_config(self): + with ftp(self.collection) as client: + client.getwelcome() diff --git a/storage_backend_ftp/i18n/storage_backend_ftp.pot b/storage_backend_ftp/i18n/storage_backend_ftp.pot new file mode 100644 index 0000000000..2f780a8c0b --- /dev/null +++ b/storage_backend_ftp/i18n/storage_backend_ftp.pot @@ -0,0 +1,121 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * storage_backend_ftp +# +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_backend_ftp +#: model:ir.model.fields,field_description:storage_backend_ftp.field_storage_backend__backend_type +msgid "Backend Type" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields,field_description:storage_backend_ftp.field_storage_backend__backend_type_env_default +msgid "Backend Type Env Default" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__ftp_encryption__tls_explicit +msgid "Explicit FTP over TLS" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__backend_type__ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__ftp_encryption__ftp +#: model_terms:ir.ui.view,arch_db:storage_backend_ftp.storage_backend_view_form +msgid "FTP" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields,field_description:storage_backend_ftp.field_storage_backend__ftp_encryption +msgid "FTP Encryption method" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields,field_description:storage_backend_ftp.field_storage_backend__ftp_server +msgid "FTP Host" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields,field_description:storage_backend_ftp.field_storage_backend__ftp_login +msgid "FTP Login" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields,field_description:storage_backend_ftp.field_storage_backend__ftp_passive +msgid "FTP Passive" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields,field_description:storage_backend_ftp.field_storage_backend__ftp_password +msgid "FTP Password" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields,field_description:storage_backend_ftp.field_storage_backend__ftp_port +msgid "FTP Port" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields,field_description:storage_backend_ftp.field_storage_backend__ftp_security +msgid "FTP security option" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__ftp_encryption__tls +msgid "Implicit FTP over TLS" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields,help:storage_backend_ftp.field_storage_backend__ftp_login +msgid "Login to connect to ftp server" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__ftp_security__none +msgid "None" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__ftp_security__sslv2 +msgid "SSLv2" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__ftp_security__sslv23 +msgid "SSLv23" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__ftp_security__sslv3 +msgid "SSLv3" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model,name:storage_backend_ftp.model_storage_backend +msgid "Storage Backend" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__ftp_security__tlsv1 +msgid "TLS" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__ftp_security__tlsv1_1 +msgid "TLSv1_1" +msgstr "" + +#. module: storage_backend_ftp +#: model:ir.model.fields.selection,name:storage_backend_ftp.selection__storage_backend__ftp_security__tlsv1_2 +msgid "TLSv1_2" +msgstr "" diff --git a/storage_backend_ftp/models/__init__.py b/storage_backend_ftp/models/__init__.py new file mode 100644 index 0000000000..f45f402268 --- /dev/null +++ b/storage_backend_ftp/models/__init__.py @@ -0,0 +1 @@ +from . import storage_backend diff --git a/storage_backend_ftp/models/storage_backend.py b/storage_backend_ftp/models/storage_backend.py new file mode 100644 index 0000000000..6743911741 --- /dev/null +++ b/storage_backend_ftp/models/storage_backend.py @@ -0,0 +1,55 @@ +# Copyright 2021 ACSONE SA/NV () +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from odoo import fields, models + + +class StorageBackend(models.Model): + _inherit = "storage.backend" + + backend_type = fields.Selection( + selection_add=[("ftp", "FTP")], ondelete={"ftp": "set default"} + ) + ftp_server = fields.Char(string="FTP Host") + ftp_port = fields.Integer(string="FTP Port", default=21) + ftp_encryption = fields.Selection( + string="FTP Encryption method", + selection=[ + ("ftp", "FTP"), + ("tls", "Implicit FTP over TLS"), + ("tls_explicit", "Explicit FTP over TLS"), + ], + default="ftp", + required=True, + ) + ftp_security = fields.Selection( + string="FTP security option", + selection=[ + ("none", "None"), + ("tlsv1", "TLS"), + ("tlsv1_1", "TLSv1_1"), + ("tlsv1_2", "TLSv1_2"), + ("sslv2", "SSLv2"), + ("sslv23", "SSLv23"), + ("sslv3", "SSLv3"), + ], + required=True, + ) + ftp_login = fields.Char(string="FTP Login", help="Login to connect to ftp server") + ftp_password = fields.Char(string="FTP Password") + ftp_passive = fields.Boolean(string="FTP Passive", default=False) + + @property + def _server_env_fields(self): + env_fields = super()._server_env_fields + env_fields.update( + { + "ftp_password": {}, + "ftp_login": {}, + "ftp_server": {}, + "ftp_port": {}, + "ftp_encryption": {}, + "ftp_security": {}, + "ftp_passive": {}, + } + ) + return env_fields diff --git a/storage_backend_ftp/readme/CONTRIBUTORS.rst b/storage_backend_ftp/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..51b8da248c --- /dev/null +++ b/storage_backend_ftp/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* François Honoré +* Lois Rilo diff --git a/storage_backend_ftp/readme/DESCRIPTION.rst b/storage_backend_ftp/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..b0b32036fc --- /dev/null +++ b/storage_backend_ftp/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Add FTP as storage backend diff --git a/storage_backend_ftp/static/description/icon.png b/storage_backend_ftp/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/storage_backend_ftp/static/description/icon.png differ diff --git a/storage_backend_ftp/static/description/index.html b/storage_backend_ftp/static/description/index.html new file mode 100644 index 0000000000..5c6201754a --- /dev/null +++ b/storage_backend_ftp/static/description/index.html @@ -0,0 +1,430 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Storage Backend FTP

+ +

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

+

Add FTP as storage backend

+

Table of contents

+ +
+

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.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Acsone SA/NV
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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.

+

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

+
+
+
+
+ + diff --git a/storage_backend_ftp/tests/__init__.py b/storage_backend_ftp/tests/__init__.py new file mode 100644 index 0000000000..9ab6aa0f49 --- /dev/null +++ b/storage_backend_ftp/tests/__init__.py @@ -0,0 +1 @@ +from . import test_ftp diff --git a/storage_backend_ftp/tests/test_ftp.py b/storage_backend_ftp/tests/test_ftp.py new file mode 100644 index 0000000000..620511cf31 --- /dev/null +++ b/storage_backend_ftp/tests/test_ftp.py @@ -0,0 +1,162 @@ +# Copyright 2021 ACSONE SA/NV () +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +# pylint: disable=missing-manifest-dependency +# disable warning on 'vcr' missing in manifest: this is only a dependency for +# dev/tests + +import errno +import ftplib +import logging +import os +from unittest import mock + +from odoo.addons.storage_backend.tests.common import BackendStorageTestMixin, CommonCase + +_logger = logging.getLogger(__name__) + +MOD_PATH = "odoo.addons.storage_backend_ftp.components.ftp_adapter" +ADAPTER_PATH = MOD_PATH + ".FTPStorageBackendAdapter" +FTP_LIB_PATH = MOD_PATH + ".ftplib" + + +class FtpCase(CommonCase, BackendStorageTestMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.backend.write( + { + "backend_type": "ftp", + "ftp_login": os.environ.get("FTP_LOGIN", "foo"), + "ftp_password": os.environ.get("FTP_PWD", "pass"), + "ftp_server": os.environ.get("FTP_HOST", "localhost"), + "ftp_port": os.environ.get("FTP_PORT", "21"), + "directory_path": "upload", + "ftp_encryption": "ftp", + } + ) + cls.case_with_subdirectory = "upload/subdirectory/here" + + @mock.patch(MOD_PATH + ".ftp_mkdirs") + @mock.patch(FTP_LIB_PATH) + def test_add(self, mocked_ftplib, mocked_mkdirs): + client = mocked_ftplib.FTP().__enter__() + # simulate errors + exc = IOError() + # general + client.cwd.side_effect = exc + with self.assertRaises(IOError): + self.backend.add("fake/path", b"fake data") + # not found + exc.errno = errno.ENOENT + client.cwd.side_effect = exc + file_data = b"fake data" + with mock.patch("io.BytesIO") as tmp_file: + self.backend.add("fake/path", file_data) + # mkdirs has been called + mocked_mkdirs.assert_called() + client.storbinary.assert_called() + tmp_file.assert_called() + tmp_file.assert_called_with(file_data) + client.storbinary.assert_called_with( + "STOR upload/fake/path", tmp_file().__enter__() + ) + + @mock.patch(FTP_LIB_PATH) + def test_get(self, mocked_ftplib): + client = mocked_ftplib.FTP().__enter__() + content = b"filecontent" + with open("/tmp/fakefile2.txt", "w+b") as fakefile: + fakefile.write(content) + + def side_effect_retrbinary(*args, **kwargs): + """ + Mock function to read tmp file. + """ + cmd, buff_write = args + with open("/tmp/fakefile2.txt", "rb") as tmp_file: + buff_write(tmp_file.read()) + + client.retrbinary.side_effect = side_effect_retrbinary + self.assertEqual(self.backend.get("fake/path"), content) + + @mock.patch(FTP_LIB_PATH) + def test_list(self, mocked_ftplib): + client = mocked_ftplib.FTP().__enter__() + # simulate errors + exc = IOError() + # general + client.nlst.side_effect = exc + with self.assertRaises(IOError): + self.backend.list_files() + # not found + exc.errno = errno.ENOENT + client.nlst.side_effect = exc + 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)] + mocked_filepaths = bad_filepaths + good_filepaths + backend = self.backend.sudo() + expected = good_filepaths[:] + expected = [backend.directory_path + "/" + path for path in good_filepaths] + self._test_find_files( + backend, ADAPTER_PATH, mocked_filepaths, r".*\.good$", expected + ) + + # Do not patch the entire ftplib otherwise the error_perm Exception + # become also a mock and then a traceback is generated on the "except ftplib.error_perm" + # because this ftplib.error_perm is not really an Exception (but a mock)! + @mock.patch(FTP_LIB_PATH + ".FTP") + def test_move_files(self, mocked_ftplib): + client = mocked_ftplib().__enter__() + # simulate file is not already there + client.nlst.side_effect = ftplib.error_perm() + to_move = "move/from/path/myfile.txt" + to_path = "move/to/path" + self.backend.move_files([to_move], to_path) + # no need to delete it + client.delete.assert_not_called() + # rename gets called + client.rename.assert_called_with( + "upload/" + to_move, "upload/" + to_move.replace("from", "to") + ) + # now try to override destination + client.nlst.side_effect = None + client.nlst.return_value = True + self.backend.move_files([to_move], to_path) + # client will delete it first + client.delete.assert_called_with(to_move.replace("from", "to")) + # then move it + client.rename.assert_called_with( + "upload/" + to_move, "upload/" + to_move.replace("from", "to") + ) + + @mock.patch(FTP_LIB_PATH) + def test_delete(self, mocked_ftplib): + client = mocked_ftplib.FTP().__enter__() + path = "delete/a/path" + self.backend.delete(path) + client.delete.assert_called_with( + os.path.join(self.backend.directory_path, path) + ) + + @mock.patch(FTP_LIB_PATH) + def test_validate_config(self, mocked_ftplib): + client = mocked_ftplib.FTP().__enter__() + self.backend.action_test_config() + client.getwelcome.assert_called() + + @mock.patch(FTP_LIB_PATH) + def test_mkd(self, mocked_ftplib): + client = mocked_ftplib.FTP().__enter__() + # simulate errors + exc = IOError() + exc.errno = errno.ENOENT + # general + client.cwd.side_effect = exc + client.mkd.side_effect = exc + with self.assertRaises(OSError): + self.backend.add("fake/path", b"fake data") + client.mkd.assert_called() diff --git a/storage_backend_ftp/views/backend_storage_view.xml b/storage_backend_ftp/views/backend_storage_view.xml new file mode 100644 index 0000000000..b20d0f87be --- /dev/null +++ b/storage_backend_ftp/views/backend_storage_view.xml @@ -0,0 +1,28 @@ + + + + storage.backend + + + + + + + + + + + + + + + + +