diff --git a/attachment_synchronize/__manifest__.py b/attachment_synchronize/__manifest__.py index 9594dbd13..6bc0943f6 100644 --- a/attachment_synchronize/__manifest__.py +++ b/attachment_synchronize/__manifest__.py @@ -22,6 +22,7 @@ 'security/ir.model.access.csv', ], 'demo': [ + "demo/attachment_synchronize_task_demo.xml", ], 'installable': True, 'application': False, diff --git a/attachment_synchronize/data/cron.xml b/attachment_synchronize/data/cron.xml index ab2f883f2..57b5e766e 100644 --- a/attachment_synchronize/data/cron.xml +++ b/attachment_synchronize/data/cron.xml @@ -1,16 +1,16 @@ - - + + Run attachment tasks import 30 minutes -1 False - + code - model.run_task_scheduler([('method_type', '=', 'import')]) + model.run_task_import_scheduler() diff --git a/attachment_synchronize/demo/attachment_synchronize_task_demo.xml b/attachment_synchronize/demo/attachment_synchronize_task_demo.xml new file mode 100644 index 000000000..8742b8d06 --- /dev/null +++ b/attachment_synchronize/demo/attachment_synchronize_task_demo.xml @@ -0,0 +1,20 @@ + + + + TEST Import + + import + delete + test_import + foo@example.org,bar@example.org + + + + TEST Export + + export + test_export + foo@example.org,bar@example.org + + + diff --git a/attachment_synchronize/models/attachment.py b/attachment_synchronize/models/attachment.py index 28df51bb5..183f965a0 100644 --- a/attachment_synchronize/models/attachment.py +++ b/attachment_synchronize/models/attachment.py @@ -8,7 +8,7 @@ import os class AttachmentQueue(models.Model): _inherit = 'attachment.queue' - task_id = fields.Many2one('storage.backend.task', string='Task') + task_id = fields.Many2one('attachment.synchronize.task', string='Task') storage_backend_id = fields.Many2one( 'storage.backend', string='Storage Backend', related='task_id.backend_id', store=True) diff --git a/attachment_synchronize/models/storage_backend.py b/attachment_synchronize/models/storage_backend.py index b77a304db..8d88d9435 100644 --- a/attachment_synchronize/models/storage_backend.py +++ b/attachment_synchronize/models/storage_backend.py @@ -6,6 +6,6 @@ from odoo import fields, models class StorageBackend(models.Model): _inherit = "storage.backend" - task_ids = fields.One2many( - "storage.backend.task", "backend_id", + synchronize_task_ids = fields.One2many( + "attachment.synchronize.task", "backend_id", string="Tasks") diff --git a/attachment_synchronize/models/task.py b/attachment_synchronize/models/task.py index eafd110ce..4212f0cfa 100644 --- a/attachment_synchronize/models/task.py +++ b/attachment_synchronize/models/task.py @@ -6,6 +6,7 @@ import os import odoo from odoo import api, fields, models, tools +from odoo.osv import expression _logger = logging.getLogger(__name__) @@ -41,9 +42,9 @@ except ImportError: _logger.warning("jinja2 not available, templating features will not work!") -class StorageBackendTask(models.Model): - _name = 'storage.backend.task' - _description = 'Storage Backend task' +class AttachmentSynchronizeTask(models.Model): + _name = 'attachment.synchronize.task' + _description = 'Attachment synchronize task' name = fields.Char(required=True) method_type = fields.Selection( @@ -118,13 +119,13 @@ class StorageBackendTask(models.Model): return render_result @api.model - def run_task_scheduler(self, domain=None): + def run_task_import_scheduler(self, domain=None): if domain is None: domain = [] - domain = expression.AND(domain, [ + domain = expression.AND([domain, [ ('method_type', '=', 'import'), ('enabled', '=', True), - ]) + ]]) for task in self.search(domain): task.run_import() @@ -133,8 +134,8 @@ class StorageBackendTask(models.Model): self.ensure_one() attach_obj = self.env['attachment.queue'] backend = self.backend_id - filenames = backend._list( - relative_path=self.filepath, pattern=self.pattern) + filepath = self.filepath or "" + filenames = backend._list(relative_path=filepath, pattern=self.pattern) if self.check_duplicated_files: filenames = self._file_to_import(filenames) total_import = 0 @@ -145,8 +146,7 @@ class StorageBackendTask(models.Model): new_env = api.Environment(new_cr, self.env.uid, self.env.context) try: - full_absolute_path = os.path.join( - self.filepath, file_name) + full_absolute_path = os.path.join(filepath, file_name) datas = backend._get_b64_data(full_absolute_path) attach_vals = self._prepare_attachment_vals( datas, file_name) @@ -156,8 +156,7 @@ class StorageBackendTask(models.Model): if self.after_import == 'rename': new_name = self._template_render( self.new_name, attachment) - new_full_path = os.path.join( - self.filepath, new_name) + new_full_path = os.path.join(filepath, new_name) elif self.after_import == 'move': new_full_path = os.path.join( self.move_path, file_name) diff --git a/attachment_synchronize/security/ir.model.access.csv b/attachment_synchronize/security/ir.model.access.csv index 5d4973cec..742f94e2f 100644 --- a/attachment_synchronize/security/ir.model.access.csv +++ b/attachment_synchronize/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_storage_backend_task_manager,storage.backend.task.manager,model_storage_backend_task,base.group_system,1,1,1,1 +access_attachment_synchronize_task_manager,attachment.synchronize.task.manager,model_attachment_synchronize_task,base.group_system,1,1,1,1 diff --git a/attachment_synchronize/tests/__init__.py b/attachment_synchronize/tests/__init__.py index e69de29bb..3845a51ae 100644 --- a/attachment_synchronize/tests/__init__.py +++ b/attachment_synchronize/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_import +from . import test_export diff --git a/attachment_synchronize/tests/common.py b/attachment_synchronize/tests/common.py new file mode 100644 index 000000000..aee8d23f3 --- /dev/null +++ b/attachment_synchronize/tests/common.py @@ -0,0 +1,38 @@ +# Copyright 2020 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import mock +import os +from odoo.addons.storage_backend.tests.common import Common + + +class SyncCommon(Common): + + def _clean_testing_directory(self): + for test_dir in [ + self.directory_input, self.directory_output, self.directory_archived]: + for filename in self.backend._list(test_dir): + self.backend._delete(os.path.join(test_dir, filename)) + + def _create_test_file(self): + self.backend._add_b64_data( + os.path.join(self.directory_input, "bar.txt"), + self.filedata, + mimetype=u"text/plain") + + def setUp(self): + super().setUp() + self.env.cr.commit = mock.Mock() + self.registry.enter_test_mode(self.env.cr) + self.directory_input = "test_import" + self.directory_output = "test_output" + self.directory_archived = "test_archived" + self._clean_testing_directory() + self._create_test_file() + self.task = self.env.ref("attachment_synchronize.import_from_filestore") + + def tearDown(self): + self.registry.leave_test_mode() + self._clean_testing_directory() + super().tearDown() diff --git a/attachment_synchronize/tests/test_export.py b/attachment_synchronize/tests/test_export.py new file mode 100644 index 000000000..b902bc188 --- /dev/null +++ b/attachment_synchronize/tests/test_export.py @@ -0,0 +1,35 @@ +# Copyright 2020 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import os +import mock +from .common import SyncCommon + +def raising_side_effect(*args, **kwargs): + raise Exception("Boom") + + +class TestExport(SyncCommon): + + def setUp(self): + super().setUp() + self.task = self.env.ref("attachment_synchronize.export_to_filestore") + self.attachment = self.env["attachment.queue"].create({ + "name": "foo.txt", + "datas_fname": "foo.txt", + "task_id": self.task.id, + "file_type": "export", + "datas": self.filedata, + }) + + def test_export(self): + self.attachment.run() + result = self.backend._list("test_export") + self.assertEqual(result, ["foo.txt"]) + + def test_failing_export(self): + with mock.patch.object(type(self.backend), "_add_b64_data", side_effect=raising_side_effect): + self.attachment.run() + self.assertEqual(self.attachment.state, "failed") + self.assertEqual(self.attachment.state_message, "Boom") diff --git a/attachment_synchronize/tests/test_import.py b/attachment_synchronize/tests/test_import.py new file mode 100644 index 000000000..d1c58d5e5 --- /dev/null +++ b/attachment_synchronize/tests/test_import.py @@ -0,0 +1,80 @@ +# Copyright 2020 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from .common import SyncCommon + + +class TestImport(SyncCommon): + + @property + def archived_files(self): + return self.backend._list(self.directory_archived) + + @property + def input_files(self): + return self.backend._list(self.directory_input) + + def _check_attachment_created(self, count=1): + attachment = self.env["attachment.queue"].search([("name", "=", "bar.txt")]) + self.assertEqual(len(attachment), count) + + def test_import_with_rename(self): + self.task.write({"after_import": "rename", "new_name": "foo.txt"}) + self.task.run_import() + self._check_attachment_created() + self.assertEqual(self.input_files, ["foo.txt"]) + self.assertEqual(self.archived_files, []) + + def test_import_with_move(self): + self.task.write({"after_import": "move", "move_path": self.directory_archived}) + self.task.run_import() + self._check_attachment_created() + self.assertEqual(self.input_files, []) + self.assertEqual(self.archived_files, ["bar.txt"]) + + def test_import_with_move_and_rename(self): + self.task.write({ + "after_import": "move_rename", + "new_name": "foo.txt", + "move_path": self.directory_archived, + }) + self.task.run_import() + self._check_attachment_created() + self.assertEqual(self.input_files, []) + self.assertEqual(self.archived_files, ["foo.txt"]) + + def test_import_with_delete(self): + self.task.write({"after_import": "delete"}) + self.task.run_import() + self._check_attachment_created() + self.assertEqual(self.input_files, []) + self.assertEqual(self.archived_files, []) + + def test_import_twice(self): + self.task.write({"after_import": "delete"}) + self.task.run_import() + self._check_attachment_created(count=1) + + self._create_test_file() + self.task.run_import() + self._check_attachment_created(count=2) + + def test_import_twice_no_duplicate(self): + self.task.write({"after_import": "delete", "check_duplicated_files": True}) + self.task.run_import() + self._check_attachment_created(count=1) + + self._create_test_file() + self.task.run_import() + self._check_attachment_created(count=1) + + def test_running_cron(self): + self.task.write({"after_import": "delete"}) + self.env["attachment.synchronize.task"].run_task_import_scheduler() + self._check_attachment_created(count=1) + + def test_running_cron_disable_task(self): + self.task.write({"after_import": "delete", "enabled": False}) + self.env["attachment.synchronize.task"].run_task_import_scheduler() + self._check_attachment_created(count=0) diff --git a/attachment_synchronize/views/storage_backend_view.xml b/attachment_synchronize/views/storage_backend_view.xml index 143aa1b46..71d159fdf 100644 --- a/attachment_synchronize/views/storage_backend_view.xml +++ b/attachment_synchronize/views/storage_backend_view.xml @@ -9,7 +9,7 @@ - + diff --git a/attachment_synchronize/views/task_view.xml b/attachment_synchronize/views/task_view.xml index 9195143aa..75b8a8321 100644 --- a/attachment_synchronize/views/task_view.xml +++ b/attachment_synchronize/views/task_view.xml @@ -1,7 +1,7 @@ - storage.backend.task + attachment.synchronize.task
@@ -37,7 +37,7 @@ - storage.backend.task + attachment.synchronize.task