diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9571e312cc..79bf5c2ebe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,6 @@ exclude: | (?x) # NOT INSTALLABLE ADDONS ^fs_file/| - ^fs_folder/| ^fs_folder_demo/| ^fs_folder_ms_drive/| ^fs_folder_webdav/| diff --git a/fs_folder/README.rst b/fs_folder/README.rst index 4af4f51770..acb4d9f1d1 100644 --- a/fs_folder/README.rst +++ b/fs_folder/README.rst @@ -21,13 +21,13 @@ Fs Folder :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/fs_folder + :target: https://github.com/OCA/storage/tree/19.0/fs_folder :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-fs_folder + :target: https://translation.odoo-community.org/projects/storage-19-0/storage-19-0-fs_folder :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| @@ -311,7 +311,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. @@ -358,6 +358,6 @@ Current `maintainer `__: |maintainer-lmignon| -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/fs_folder/__manifest__.py b/fs_folder/__manifest__.py index 2022a3c53d..ad05e13987 100644 --- a/fs_folder/__manifest__.py +++ b/fs_folder/__manifest__.py @@ -5,13 +5,11 @@ "name": "Fs Folder", "summary": """A module to link to Odoo records and manage from record forms forlders from external file systems """, - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "license": "LGPL-3", "author": "ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/storage", - "depends": [ - "fs_storage", - ], + "depends": ["fs_storage", "bus"], "data": [ "views/fs_storage.xml", ], @@ -21,6 +19,5 @@ ], }, "demo": [], - "installable": False, "maintainers": ["lmignon"], } diff --git a/fs_folder/controllers/fs_folder_controller.py b/fs_folder/controllers/fs_folder_controller.py index ab384034c9..8e7605311d 100644 --- a/fs_folder/controllers/fs_folder_controller.py +++ b/fs_folder/controllers/fs_folder_controller.py @@ -29,7 +29,7 @@ def get_file(self, res_id, res_model, field_name, path, download=False, **kwargs @http.route( "/fs_folder/get_children///", - type="json", + type="jsonrpc", auth="user", methods=["POST"], ) @@ -43,7 +43,7 @@ def get_children(self, res_id, res_model, field_name, path=""): @http.route( "/fs_folder/add_folder///", - type="json", + type="jsonrpc", auth="user", methods=["POST"], ) @@ -54,7 +54,7 @@ def add_folder(self, res_id, res_model, field_name, path, name): @http.route( "/fs_folder/delete///", - type="json", + type="jsonrpc", auth="user", methods=["POST"], ) @@ -65,7 +65,7 @@ def delete(self, res_id, res_model, field_name, path, name): @http.route( "/fs_folder/move///", - type="json", + type="jsonrpc", auth="user", methods=["POST"], ) @@ -83,7 +83,7 @@ def move_file(self, res_id, res_model, field_name, path, origin_path, record): @http.route( "/fs_folder/copy///", - type="json", + type="jsonrpc", auth="user", methods=["POST"], ) @@ -101,7 +101,7 @@ def copy_file(self, res_id, res_model, field_name, path, origin_path, record): @http.route( "/fs_folder/rename///", - type="json", + type="jsonrpc", auth="user", methods=["POST"], ) @@ -116,7 +116,7 @@ def rename(self, res_id, res_model, field_name, path, name, new_name): @http.route( "/fs_folder/upload///", - type="json", + type="jsonrpc", auth="user", methods=["POST"], ) @@ -132,7 +132,7 @@ def upload(self, res_id, res_model, field_name, path, name, data): @http.route( "/fs_folder/initialize///", - type="json", + type="jsonrpc", auth="user", methods=["POST"], ) @@ -143,7 +143,7 @@ def initialize(self, res_id, res_model, field_name): @http.route( "/fs_folder/unlink_folder///", - type="json", + type="jsonrpc", auth="user", methods=["POST"], ) @@ -154,7 +154,7 @@ def unlink_folder(self, res_id, res_model, field_name): @http.route( "/fs_folder/delete_folder///", - type="json", + type="jsonrpc", auth="user", methods=["POST"], ) diff --git a/fs_folder/fields.py b/fs_folder/fields.py index d539f4eac2..079344a8d4 100644 --- a/fs_folder/fields.py +++ b/fs_folder/fields.py @@ -8,7 +8,8 @@ import fsspec -from odoo import api, fields, models, registry +from odoo import api, fields, models +from odoo.modules.registry import Registry from odoo.tools.misc import SENTINEL, Sentinel from odoo.tools.sql import pg_varchar @@ -356,7 +357,7 @@ def create_value_in_fs(self, records: models.BaseModel) -> list[FsFolderValue]: fs.mkdir(path, **kwargs) def clean_up_folder(path, storage_code, dbname, user_id): - db_registry = registry(dbname) + db_registry = Registry(dbname) with db_registry.cursor() as cr: env = api.Environment(cr, user_id, {}) fs = env["fs.storage"].get_fs_by_code(storage_code) diff --git a/fs_folder/models/fs_folder_field_web_api.py b/fs_folder/models/fs_folder_field_web_api.py index 5a93513218..51834c9e58 100644 --- a/fs_folder/models/fs_folder_field_web_api.py +++ b/fs_folder/models/fs_folder_field_web_api.py @@ -7,7 +7,7 @@ import fsspec from werkzeug import Response -from odoo import _, api, models +from odoo import api, models from odoo.exceptions import AccessError, UserError from ..fs_stream import FsStream @@ -31,10 +31,10 @@ def _check_field_access(self, res_id, res_model, field_name, access): :param access: the access rights to check """ if res_model not in self.env: - raise AccessError(_("Unknown model")) + raise AccessError(self.env._("Unknown model")) record = self.env[res_model].browse(res_id) if field_name not in record._fields: - raise AccessError(_("Unknown field")) + raise AccessError(self.env._("Unknown field")) record.check_access(access) @api.model @@ -76,7 +76,9 @@ def _get_fs( if field.type == "fs_folder": fs = record[field_name].fs if not fs: - raise ValueError(_("The field is not an external filesystem field.")) + raise ValueError( + self.env._("The field is not an external filesystem field.") + ) return fs @api.model @@ -113,13 +115,13 @@ def get_children(self, res_id, res_model, field_name, path, **kwargs) -> list[di return fs.ls(path, detail=True) except Exception as e: raise UserError( - _( - "An error occurred while listing files: '%s'\n" + self.env._( + "An error occurred while listing files: '%(exception)s'\n" "This might happen if the folder was moved, renamed or deleted " "on the external storage.\n" - "If this is expected you might want to unlink this folder." + "If this is expected you might want to unlink this folder.", + exception=e, ) - % e ) from e @api.model diff --git a/fs_folder/models/fs_storage.py b/fs_folder/models/fs_storage.py index db7e57daca..e8793624d8 100644 --- a/fs_folder/models/fs_storage.py +++ b/fs_folder/models/fs_storage.py @@ -2,7 +2,7 @@ # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). import re -from odoo import _, api, fields, models, tools +from odoo import api, fields, models, tools from odoo.exceptions import UserError, ValidationError @@ -33,18 +33,23 @@ def _check_fs_name_sanitization_replace_char(self): if rc and self._invalid_fs_name_chars_re_pattern.findall(rc): raise ValidationError( - _("The character to use as replacement can not be one of '%s'") - % self._invalid_fs_name_chars + self.env._( + "The character to use as replacement can not be one of " + "'%(invalid_chars)s'", + invalid_chars=self._invalid_fs_name_chars, + ) ) @api.constrains("use_as_default_for_fs_contents") def _check_use_as_default_for_fs_contents(self): # constrains are checked in python since values can be provided by # the server environment - defaults = self.search([]).filtered("use_as_default_for_fs_contents") + defaults = self.search([]).filtered("use_as_default_for_fs_contents") # pylint: disable=no-search-all if len(defaults) > 1: raise ValidationError( - _("Only one storage can be used as default for filesystem contents.") + self.env._( + "Only one storage can be used as default for filesystem contents." + ) ) @property @@ -60,6 +65,7 @@ def _server_env_fields(self): @api.model @tools.ormcache() def get_storage_code_for_fs_content_fallback(self) -> str | None: + # pylint: disable=no-search-all storages = ( self.sudo() .search([]) @@ -80,7 +86,7 @@ def get_default_storage_code_for_fs_content(self, model_name, field_name) -> str storage_code = self.get_storage_code_for_fs_content_fallback() if not storage_code: raise ValueError( - _( + self.env._( "No default storage found for the content of the external " "filesystem fields for model %(model)s and field %(field)s. " "Please set a default storage in the filesystem storage " @@ -105,7 +111,7 @@ def is_fs_name_valid(self, name, raise_if_invalid=False) -> bool: invalid = self._invalid_fs_name_chars_re_pattern.findall(name) if invalid and raise_if_invalid: raise UserError( - _( + self.env._( "The name '%(name)s' contains invalid characters" " %(invalid_chars)s.\n" "The following chars are not allowed: %(all_invalid_chars)s", diff --git a/fs_folder/static/description/index.html b/fs_folder/static/description/index.html index e7ba62b0b8..2eacfad379 100644 --- a/fs_folder/static/description/index.html +++ b/fs_folder/static/description/index.html @@ -374,7 +374,7 @@

Fs Folder

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:c4cce3679f458dfca0f84f2626680d41bd9ae5049b0fde289ecb07ee4359a869 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

If you need to link some specific models to a specific folder into an external filesystem and be able to manage the content of this folder from the model form view, this module is for you.

@@ -645,7 +645,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.

@@ -682,7 +682,7 @@

Maintainers

promote its widespread use.

Current maintainer:

lmignon

-

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/fs_folder/tests/common.py b/fs_folder/tests/common.py index ef546898a8..50792876c8 100644 --- a/fs_folder/tests/common.py +++ b/fs_folder/tests/common.py @@ -3,8 +3,7 @@ import shutil import tempfile -from odoo_test_helper import FakeModelLoader - +from odoo.orm.model_classes import add_to_registry from odoo.tests.common import TransactionCase from odoo.addons.base.tests.common import BaseCommon @@ -15,14 +14,22 @@ class FsFolderTestCase(TransactionCase): def setUpClass(cls): super().setUpClass() cls.env = cls.env["base"].with_context(**BaseCommon.default_env_context()).env - cls.loader = FakeModelLoader(cls.env, cls.__module__) - cls.loader.backup_registry() - cls.addClassCleanup(cls.loader.restore_registry) from .models import FsTestModel, FsTestModelInherits, FsTestModelRelated - cls.loader.update_registry( - (FsTestModel, FsTestModelInherits, FsTestModelRelated) - ) + model_names = [ + FsTestModel._name, + FsTestModelInherits._name, + FsTestModelRelated._name, + ] + add_to_registry(cls.registry, FsTestModel) + add_to_registry(cls.registry, FsTestModelInherits) + add_to_registry(cls.registry, FsTestModelRelated) + cls.registry._setup_models__(cls.env.cr, model_names) + cls.registry.init_models(cls.env.cr, model_names, {"models_to_check": True}) + cls.addClassCleanup(cls.registry.__delitem__, FsTestModel._name) + cls.addClassCleanup(cls.registry.__delitem__, FsTestModelInherits._name) + cls.addClassCleanup(cls.registry.__delitem__, FsTestModelRelated._name) + cls.fs_test_model = cls.env[FsTestModel._name] cls.fs_test_model_inherits = cls.env[FsTestModelInherits._name] cls.fs_test_model_related = cls.env[FsTestModelRelated._name] diff --git a/fs_folder/tests/test_fs_folder_field_value_adapter.py b/fs_folder/tests/test_fs_folder_field_value_adapter.py index 4003a5daa8..a8e2ea5c08 100644 --- a/fs_folder/tests/test_fs_folder_field_value_adapter.py +++ b/fs_folder/tests/test_fs_folder_field_value_adapter.py @@ -3,8 +3,6 @@ import os import shutil -from odoo import models - from odoo.addons.fs_storage.rooted_dir_file_system import RootedDirFileSystem from .common import FsFolderTestCase @@ -15,23 +13,34 @@ class TestFsFodlerFieldValueAdapter(FsFolderTestCase): def setUpClass(cls): super().setUpClass() - class FsTestFsFolderFieldValueAdapter(models.AbstractModel): - _inherit = "fs.folder.field.value.adapter" + # We can't use add_to_registry here because the model has no _name of + # its own (_inherit), so instead we patch the method directly and restore it + # after the test. + adapter_cls = cls.registry["fs.folder.field.value.adapter"] + original_created = adapter_cls._created_folder_name_to_stored_value + original_rooted = adapter_cls._get_rooted_dir_file_system - def _created_folder_name_to_stored_value(self, path, storage_code, fs): - # for the test we inverse the path content - path = path.lstrip("/")[::-1] - return super()._created_folder_name_to_stored_value( - path, storage_code, fs - ) + def reversed_created(self, path, storage_code, fs): + path = path.lstrip("/")[::-1] + return original_created(self, path, storage_code, fs) - def _get_rooted_dir_file_system(self, value, fs): - ref = value.ref - if ref: - ref = ref[::-1] - return RootedDirFileSystem(path=ref, fs=fs) + def reversed_rooted(self, value, fs): + ref = value.ref + if ref: + ref = ref[::-1] + return RootedDirFileSystem(path=ref, fs=fs) - cls.loader.update_registry((FsTestFsFolderFieldValueAdapter,)) + adapter_cls._created_folder_name_to_stored_value = reversed_created + adapter_cls._get_rooted_dir_file_system = reversed_rooted + cls.addClassCleanup( + setattr, + adapter_cls, + "_created_folder_name_to_stored_value", + original_created, + ) + cls.addClassCleanup( + setattr, adapter_cls, "_get_rooted_dir_file_system", original_rooted + ) def tearDown(self) -> None: super().tearDown() diff --git a/fs_folder/tests/test_fs_storage.py b/fs_folder/tests/test_fs_storage.py index 39d9d53109..d1de9270a4 100644 --- a/fs_folder/tests/test_fs_storage.py +++ b/fs_folder/tests/test_fs_storage.py @@ -5,6 +5,7 @@ from odoo.tests.common import TransactionCase from odoo.addons.base.tests.common import BaseCommon +from odoo.addons.fs_storage.models.fs_storage import FSStorage class TestFsStorage(TransactionCase): @@ -12,7 +13,13 @@ class TestFsStorage(TransactionCase): def setUpClass(cls): super().setUpClass() cls.env = cls.env["base"].with_context(**BaseCommon.default_env_context()).env - cls.backend = cls.env.ref("fs_storage.fs_storage_demo") + cls.backend: FSStorage = cls.env["fs.storage"].create( + { + "name": "Odoo Filesystem Backend", + "protocol": "odoofs", + "code": "odoofs", + } + ) def test_is_fs_name_valid(self): self.assertFalse(self.backend.is_fs_name_valid(r'my\/:*?"<>| directory'))