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 Backend SFTP
Odoo addon: storage_backend_sftp
## Installation
```bash
pip install odoo-bringout-oca-storage-storage_backend_sftp
```
## Dependencies
This addon depends on:
- storage_backend
## Manifest Information
- **Name**: Storage Backend SFTP
- **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_backend_sftp`.
## 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_backend_sftp Module - storage_backend_sftp
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_backend_sftp. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

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_backend_sftp or install in UI.

View file

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

View file

@ -0,0 +1,12 @@
# Models
Detected core models and extensions in storage_backend_sftp.
```mermaid
classDiagram
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_backend_sftp. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon storage_backend_sftp
- License: LGPL-3

View file

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

View file

@ -0,0 +1,8 @@
# Security
This module does not define custom security rules or access controls beyond Odoo defaults.
Default Odoo security applies:
- Base user access through standard groups
- Model access inherited from dependencies
- No custom row-level security rules

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_backend_sftp
```

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_backend_sftp"
version = "16.0.0"
description = "Storage Backend SFTP - Implement SFTP Storage"
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_backend_sftp"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,44 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
=====================
Storage backend SFTP
=====================
Add the possibility to store and get data from an SFTP for your storage backend
Installation
============
To install this module, you need to:
#. (root) pip install paramiko
Known issues / Roadmap
======================
Update README with the last model of README when migration to v11 in OCA
Credits
=======
Contributors
------------
* Sebastien Beau <sebastien.beau@akretion.com>
* Raphaël Reverdy <raphael.reverdy@akretion.com>
* Cédric Pigeon <cedric.pigeon@acsone.eu>
* Simone Orsi <simone.orsi@camptocamp.com>
Maintainer
----------
* Akretion

View file

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

View file

@ -0,0 +1,17 @@
# 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 Backend SFTP",
"summary": "Implement SFTP Storage",
"version": "16.0.1.0.1",
"category": "Storage",
"website": "https://github.com/OCA/storage",
"author": " Akretion,Odoo Community Association (OCA)",
"license": "LGPL-3",
"installable": True,
"external_dependencies": {"python": ["paramiko"]},
"depends": ["storage_backend"],
"data": ["views/backend_storage_view.xml"],
}

View file

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

View file

@ -0,0 +1,129 @@
# Copyright 2017 Akretion (http://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# Copyright 2019 Camptocamp SA (http://www.camptocamp.com).
# Copyright 2020 ACSONE SA/NV (<http://acsone.eu>)
# @author Simone Orsi <simahawk@gmail.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import errno
import logging
import os
from contextlib import contextmanager
from io import StringIO
from odoo.addons.component.core import Component
_logger = logging.getLogger(__name__)
try:
import paramiko
except ImportError as err: # pragma: no cover
_logger.debug(err)
def sftp_mkdirs(client, path, mode=511):
try:
client.mkdir(path, mode)
except IOError as e:
if e.errno == errno.ENOENT and path:
sftp_mkdirs(client, os.path.dirname(path), mode=mode)
client.mkdir(path, mode)
else:
raise # pragma: no cover
def load_ssh_key(ssh_key_buffer):
for pkey_class in (
paramiko.RSAKey,
paramiko.DSSKey,
paramiko.ECDSAKey,
paramiko.Ed25519Key,
):
try:
return pkey_class.from_private_key(ssh_key_buffer)
except paramiko.SSHException:
ssh_key_buffer.seek(0) # reset the buffer "file"
raise Exception("Invalid ssh private key")
@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()
class SFTPStorageBackendAdapter(Component):
_name = "sftp.adapter"
_inherit = "base.storage.adapter"
_usage = "sftp"
def add(self, relative_path, data, **kwargs):
with sftp(self.collection) as client:
full_path = self._fullpath(relative_path)
dirname = os.path.dirname(full_path)
if dirname:
try:
client.stat(dirname)
except IOError as e:
if e.errno == errno.ENOENT:
sftp_mkdirs(client, dirname)
else:
raise # pragma: no cover
remote_file = client.open(full_path, "w")
remote_file.write(data)
remote_file.close()
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()
# TODO: shouldn't we close the file?
return data
def list(self, relative_path):
full_path = self._fullpath(relative_path)
with sftp(self.collection) as client:
try:
return client.listdir(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 sftp(self.collection) as client:
for sftp_file in files:
dest_file_path = os.path.join(
destination_path, os.path.basename(sftp_file)
)
# Remove existing file at the destination path (an error is raised
# otherwise)
try:
client.lstat(dest_file_path)
except FileNotFoundError:
_logger.debug("destination %s is free", dest_file_path)
else:
client.unlink(dest_file_path)
# Move the file using absolute filepaths
client.rename(fp(sftp_file), fp(dest_file_path))
def delete(self, relative_path):
full_path = self._fullpath(relative_path)
with sftp(self.collection) as client:
return client.remove(full_path)
def validate_config(self):
with sftp(self.collection) as client:
client.listdir()

View file

@ -0,0 +1,87 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * storage_backend_sftp
#
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_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type
msgid "Backend Type"
msgstr "Tip pozadine"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type_env_default
msgid "Backend Type Env Default"
msgstr "Zadano okruženje tipa pozadine"
#. module: storage_backend_sftp
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
msgid ""
"It's recommended to not store the key here but to provide it via secret env "
"variable. See `server_environment` docs."
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_login
msgid "Login to connect to sftp server"
msgstr "Prijava za povezivanje na SFTP server"
#. module: storage_backend_sftp
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__pwd
msgid "Password"
msgstr "Zaporka"
#. module: storage_backend_sftp
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__ssh_key
msgid "Private key"
msgstr "Privatni ključ"
#. module: storage_backend_sftp
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__backend_type__sftp
#: model_terms:ir.ui.view,arch_db:storage_backend_sftp.storage_backend_view_form
msgid "SFTP"
msgstr "SFTP"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_auth_method
msgid "SFTP Authentification Method"
msgstr "SFTP metoda autentifikacije"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_server
msgid "SFTP Host"
msgstr "SFTP domaćin"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_login
msgid "SFTP Login"
msgstr "SFTP prijava"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_password
msgid "SFTP Password"
msgstr "SFTP Password"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_port
msgid "SFTP Port"
msgstr "SFTP Port"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
msgid "SSH private key"
msgstr "SSH privatni ključ"
#. module: storage_backend_sftp
#: model:ir.model,name:storage_backend_sftp.model_storage_backend
msgid "Storage Backend"
msgstr "Pozadina skladištenja"

View file

@ -0,0 +1,92 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * storage_backend_sftp
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-06-04 09:26+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.10.4\n"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type
msgid "Backend Type"
msgstr "Tipo backend"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type_env_default
msgid "Backend Type Env Default"
msgstr "Tipo backend ambiente predefinito"
#. module: storage_backend_sftp
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
msgid ""
"It's recommended to not store the key here but to provide it via secret env "
"variable. See `server_environment` docs."
msgstr ""
"Si raccomanda si non salvare qui la chiave ma di fornirla attraverso una "
"variabile di ambiente segreta. Vedere documentazione 'server_enviroment'."
#. module: storage_backend_sftp
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_login
msgid "Login to connect to sftp server"
msgstr "Accedere per collegarsi al server SFTP"
#. module: storage_backend_sftp
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__pwd
msgid "Password"
msgstr "Password"
#. module: storage_backend_sftp
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__ssh_key
msgid "Private key"
msgstr "Chiave privata"
#. module: storage_backend_sftp
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__backend_type__sftp
#: model_terms:ir.ui.view,arch_db:storage_backend_sftp.storage_backend_view_form
msgid "SFTP"
msgstr "SFTP"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_auth_method
msgid "SFTP Authentification Method"
msgstr "Metodo autenticazione SFTP"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_server
msgid "SFTP Host"
msgstr "Host SFTP"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_login
msgid "SFTP Login"
msgstr "Accesso SFTP"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_password
msgid "SFTP Password"
msgstr "Password SFTP"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_port
msgid "SFTP Port"
msgstr "Porta SFTP"
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
msgid "SSH private key"
msgstr "Chiave privata SSH"
#. module: storage_backend_sftp
#: model:ir.model,name:storage_backend_sftp.model_storage_backend
msgid "Storage Backend"
msgstr "Backend deposito"

View file

@ -0,0 +1,87 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * storage_backend_sftp
#
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_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type
msgid "Backend Type"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__backend_type_env_default
msgid "Backend Type Env Default"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
msgid ""
"It's recommended to not store the key here but to provide it via secret env "
"variable. See `server_environment` docs."
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields,help:storage_backend_sftp.field_storage_backend__sftp_login
msgid "Login to connect to sftp server"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__pwd
msgid "Password"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__sftp_auth_method__ssh_key
msgid "Private key"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields.selection,name:storage_backend_sftp.selection__storage_backend__backend_type__sftp
#: model_terms:ir.ui.view,arch_db:storage_backend_sftp.storage_backend_view_form
msgid "SFTP"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_auth_method
msgid "SFTP Authentification Method"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_server
msgid "SFTP Host"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_login
msgid "SFTP Login"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_password
msgid "SFTP Password"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_port
msgid "SFTP Port"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model.fields,field_description:storage_backend_sftp.field_storage_backend__sftp_ssh_private_key
msgid "SSH private key"
msgstr ""
#. module: storage_backend_sftp
#: model:ir.model,name:storage_backend_sftp.model_storage_backend
msgid "Storage Backend"
msgstr ""

View file

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

View file

@ -0,0 +1,48 @@
# 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).
from odoo import fields, models
class StorageBackend(models.Model):
_inherit = "storage.backend"
backend_type = fields.Selection(
selection_add=[("sftp", "SFTP")], ondelete={"sftp": "set default"}
)
sftp_server = fields.Char(string="SFTP Host")
sftp_port = fields.Integer(string="SFTP Port", default=22)
sftp_auth_method = fields.Selection(
string="SFTP Authentification Method",
selection=[("pwd", "Password"), ("ssh_key", "Private key")],
default="pwd",
required=True,
)
sftp_login = fields.Char(
string="SFTP Login", help="Login to connect to sftp server"
)
sftp_password = fields.Char(string="SFTP Password")
sftp_ssh_private_key = fields.Text(
string="SSH private key",
help="It's recommended to not store the key here "
"but to provide it via secret env variable. "
"See `server_environment` docs.",
)
@property
def _server_env_fields(self):
env_fields = super()._server_env_fields
env_fields.update(
{
"sftp_password": {},
"sftp_login": {},
"sftp_server": {},
"sftp_port": {},
"sftp_auth_method": {},
"sftp_ssh_private_key": {},
}
)
return env_fields

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

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

View file

@ -0,0 +1,120 @@
# Copyright 2017 Akretion (http://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# Copyright 2019 Camptocamp (http://www.camptocamp.com).
# @author Simone Orsi <simone.orsi@camptocamp.com>
# 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
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_sftp.components.sftp_adapter"
ADAPTER_PATH = MOD_PATH + ".SFTPStorageBackendAdapter"
PARAMIKO_PATH = MOD_PATH + ".paramiko"
class SftpCase(CommonCase, BackendStorageTestMixin):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.backend.write(
{
"backend_type": "sftp",
"sftp_login": "foo",
"sftp_password": "pass",
"sftp_server": os.environ.get("SFTP_HOST", "localhost"),
"sftp_port": os.environ.get("SFTP_PORT", "2222"),
"directory_path": "upload",
}
)
cls.case_with_subdirectory = "upload/subdirectory/here"
@mock.patch(MOD_PATH + ".sftp_mkdirs")
@mock.patch(PARAMIKO_PATH)
def test_add(self, mocked_paramiko, mocked_mkdirs):
client = mocked_paramiko.SFTPClient.from_transport()
# simulate errors
exc = IOError()
# general
client.stat.side_effect = exc
with self.assertRaises(IOError):
self.backend.add("fake/path", b"fake data")
# 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", "r") as thefile:
self.assertEqual(thefile.read(), "fake data")
@mock.patch(PARAMIKO_PATH)
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", "r")
self.assertEqual(self.backend.get("fake/path"), "filecontent")
@mock.patch(PARAMIKO_PATH)
def test_list(self, mocked_paramiko):
client = mocked_paramiko.SFTPClient.from_transport()
# simulate errors
exc = IOError()
# general
client.listdir.side_effect = exc
with self.assertRaises(IOError):
self.backend.list_files()
# not found
exc.errno = errno.ENOENT
client.listdir.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
)
@mock.patch(PARAMIKO_PATH)
def test_move_files(self, mocked_paramiko):
client = mocked_paramiko.SFTPClient.from_transport()
# simulate file is not already there
client.lstat.side_effect = FileNotFoundError()
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.unlink.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.lstat.side_effect = None
client.lstat.return_value = True
self.backend.move_files([to_move], to_path)
# client will delete it first
client.unlink.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")
)

View file

@ -0,0 +1,31 @@
<?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">
<group name="config" position="after">
<group
name="sftp"
string="SFTP"
attrs="{'invisible': [('backend_type', '!=', 'sftp')]}"
>
<field name="sftp_server" />
<field name="sftp_port" />
<field name="sftp_auth_method" />
<field name="sftp_login" />
<field
name="sftp_password"
password="True"
attrs="{'invisible': [('sftp_auth_method', '!=', 'pwd')]}"
/>
<field
name="sftp_ssh_private_key"
password="True"
attrs="{'invisible': [('sftp_auth_method', '!=', 'ssh_key')]}"
/>
</group>
</group>
</field>
</record>
</odoo>