From 8eeb0ca7ef8db06ad4e1938068fa2013f32ca9b1 Mon Sep 17 00:00:00 2001 From: Yu Weng Date: Tue, 7 Nov 2023 16:04:23 +0100 Subject: [PATCH] [ADD] module dms_version 14.0 --- dms_version/README.rst | 96 ++++ dms_version/__init__.py | 5 + dms_version/__manifest__.py | 27 ++ dms_version/models/__init__.py | 6 + dms_version/models/dms_directory.py | 26 ++ dms_version/models/dms_file.py | 283 ++++++++++++ dms_version/models/dms_storage.py | 14 + dms_version/readme/CONTRIBUTORS.rst | 4 + dms_version/readme/DESCRIPTION.rst | 1 + dms_version/readme/ROADMAP.rst | 1 + dms_version/readme/USAGE.rst | 1 + dms_version/security/ir.model.access.csv | 2 + dms_version/security/security.xml | 6 + dms_version/static/description/icon.png | Bin 0 -> 9562 bytes dms_version/static/description/icon.svg | 1 + dms_version/static/description/index.html | 434 ++++++++++++++++++ dms_version/tests/__init__.py | 1 + dms_version/tests/test_dms_file.py | 180 ++++++++ dms_version/views/dms_directory_view.xml | 27 ++ dms_version/views/dms_file_view.xml | 164 +++++++ dms_version/views/dms_storage_view.xml | 27 ++ dms_version/wizard/__init__.py | 1 + dms_version/wizard/restore_old_revision.py | 33 ++ .../wizard/restore_old_revision_view.xml | 57 +++ 24 files changed, 1397 insertions(+) create mode 100644 dms_version/README.rst create mode 100644 dms_version/__init__.py create mode 100644 dms_version/__manifest__.py create mode 100644 dms_version/models/__init__.py create mode 100644 dms_version/models/dms_directory.py create mode 100644 dms_version/models/dms_file.py create mode 100644 dms_version/models/dms_storage.py create mode 100644 dms_version/readme/CONTRIBUTORS.rst create mode 100644 dms_version/readme/DESCRIPTION.rst create mode 100644 dms_version/readme/ROADMAP.rst create mode 100644 dms_version/readme/USAGE.rst create mode 100644 dms_version/security/ir.model.access.csv create mode 100644 dms_version/security/security.xml create mode 100644 dms_version/static/description/icon.png create mode 100644 dms_version/static/description/icon.svg create mode 100644 dms_version/static/description/index.html create mode 100644 dms_version/tests/__init__.py create mode 100644 dms_version/tests/test_dms_file.py create mode 100644 dms_version/views/dms_directory_view.xml create mode 100644 dms_version/views/dms_file_view.xml create mode 100644 dms_version/views/dms_storage_view.xml create mode 100644 dms_version/wizard/__init__.py create mode 100644 dms_version/wizard/restore_old_revision.py create mode 100644 dms_version/wizard/restore_old_revision_view.xml diff --git a/dms_version/README.rst b/dms_version/README.rst new file mode 100644 index 000000000..81abfb282 --- /dev/null +++ b/dms_version/README.rst @@ -0,0 +1,96 @@ +================================== +Document Management System Version +================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-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%2Fdms-lightgray.png?logo=github + :target: https://github.com/OCA/dms/tree/13.0/dms_version + :alt: OCA/dms +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/dms-13-0/dms-13-0-dms_version + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/292/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Documents Versioning enables version control for the document management system. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Go to any file form and if the file has an older version its possible to revert it. + +Known issues / Roadmap +====================== + +Creating an addon to compress attachments could be useful. + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* MuK IT +* Tecnativa +* elego + +Contributors +~~~~~~~~~~~~ + +* Mathias Markl +* `Tecnativa `_: + + * Víctor Martínez + +* `AgentERP `_: + + * Maria Sparenberg + +* `Elego `_: + + * Yu Weng + +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/dms `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/dms_version/__init__.py b/dms_version/__init__.py new file mode 100644 index 000000000..c7b0b610d --- /dev/null +++ b/dms_version/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2017-2020 MuK IT GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import wizard diff --git a/dms_version/__manifest__.py b/dms_version/__manifest__.py new file mode 100644 index 000000000..53709d1b5 --- /dev/null +++ b/dms_version/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright 2017-2020 MuK IT GmbH +# Copyright 2021 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Document Management System Version", + "summary": """Version Control for Documents""", + "version": "14.0.1.0.0", + "category": "Document Management", + "license": "AGPL-3", + "website": "https://github.com/OCA/dms", + "author": """MuK IT, + Tecnativa, + elego Software Solutions Gmbh, + Odoo Community Association (OCA)""", + "depends": ["dms", "base_revision"], + "data": [ + "security/ir.model.access.csv", + "security/security.xml", + "wizard/restore_old_revision_view.xml", + "views/dms_file_view.xml", + "views/dms_directory_view.xml", + "views/dms_storage_view.xml", + ], + "maintainers": ["victoralmau"], + "application": False, +} diff --git a/dms_version/models/__init__.py b/dms_version/models/__init__.py new file mode 100644 index 000000000..7c3320a18 --- /dev/null +++ b/dms_version/models/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2017-2020 MuK IT GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import dms_file +from . import dms_directory +from . import dms_storage diff --git a/dms_version/models/dms_directory.py b/dms_version/models/dms_directory.py new file mode 100644 index 000000000..d8bbfccc7 --- /dev/null +++ b/dms_version/models/dms_directory.py @@ -0,0 +1,26 @@ +# Copyright 2017-2020 MuK IT GmbH +# Copyright 2021 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class DmsDirectory(models.Model): + _inherit = "dms.directory" + + has_versioning = fields.Boolean( + default=False, + help="Indicates if files have an active version control.", + ) + + @api.onchange("storage_id") + def _onchange_storage_id(self): + for record in self: + if record.storage_id.save_type != "attachment" and record.is_root_directory: + record.has_versioning = record.storage_id.has_versioning + + @api.onchange("parent_id") + def _onchange_parent_id(self): + for record in self: + if record.parent_id: + record.has_versioning = record.parent_id.has_versioning diff --git a/dms_version/models/dms_file.py b/dms_version/models/dms_file.py new file mode 100644 index 000000000..13e62ae75 --- /dev/null +++ b/dms_version/models/dms_file.py @@ -0,0 +1,283 @@ +# Copyright 2017-2020 MuK IT GmbH +# Copyright 2021 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, exceptions, fields, models + + +class DmsFile(models.Model): + _name = "dms.file" + _inherit = ["dms.file", "base.revision"] + + name = fields.Char( + tracking=True, + ) + directory_id = fields.Many2one( + tracking=True, + ) + storage_id = fields.Many2one( + tracking=True, + ) + extension = fields.Char( + tracking=True, + ) + mimetype = fields.Char( + tracking=True, + ) + category_id = fields.Many2one( + tracking=True, + ) + current_revision_id = fields.Many2one( + comodel_name="dms.file", + readonly=True, + ) + old_revision_ids = fields.One2many( + comodel_name="dms.file", + ) + has_versioning = fields.Boolean( + string="Has Versioning", + tracking=True, + ) + can_edit_has_versioning = fields.Boolean( + compute="_compute_can_edit_has_versioning", + default=True, + ) + origin_id = fields.Many2one( + comodel_name="dms.file", + ) + all_revision_ids = fields.One2many( + comodel_name="dms.file", + inverse_name="origin_id", + readonly=True, + domain=["|", ("active", "=", False), ("active", "=", True)], + context={"active_test": False}, + ) + all_revision_count = fields.Integer( + compute="_compute_all_revision_count", + ) + parent_id = fields.Many2one( + comodel_name="dms.file", + ) + has_current_revision = fields.Boolean( + compute="_compute_has_current_revision", store=True + ) + + # (overwrite) sql_constraints revision_unique from module base_revision + _sql_constraints = [ + ( + "revision_unique", + "Check(1=1)", + "Reference and revision must be unique.", + ) + ] + + @api.constrains("unrevisioned_name", "revision_number", "directory_id") + def check_unique_name_revision_number(self): + for rec in self: + if rec.origin_id: + res = self.search_count( + [ + ("unrevisioned_name", "=", rec.unrevisioned_name), + ("revision_number", "=", rec.revision_number), + ("directory_id", "=", rec.directory_id.id), + "|", + ("active", "=", False), + ("active", "=", True), + ] + ) + if res > 1: + raise exceptions.UserError( + _("Reference and revision must be unique in a directory.") + ) + + @api.constrains("active", "origin_id") + def check_unique_active_file(self): + for rec in self: + if rec.origin_id: + res = self.search_count( + [ + ("origin_id", "=", rec.origin_id.id), + ] + ) + if res > 1: + raise exceptions.UserError( + _( + 'Found more active version of file "%s".' + % rec.origin_id.name + ) + ) + + @api.constrains("revision_number", "origin_id") + def check_unique_origin_revision_number(self): + for rec in self: + if rec.origin_id: + res = self.search_count( + [ + ("origin_id", "=", rec.origin_id.id), + ("revision_number", "=", rec.revision_number), + "|", + ("active", "=", False), + ("active", "=", True), + ] + ) + if res > 1: + raise exceptions.UserError( + _('Revision number "%s" must be unique.' % rec.revision_number) + ) + + def _compute_can_edit_has_versioning(self): + can_edit = self.user_has_groups("dms_version.group_dms_version_user") + for rec in self: + rec.can_edit_has_versioning = can_edit + + @api.depends("current_revision_id", "current_revision_id.active") + def _compute_has_current_revision(self): + for rec in self: + rec.has_current_revision = False + if rec.current_revision_id and rec.current_revision_id.active: + rec.has_current_revision = True + elif not rec.current_revision_id and rec.has_versioning and rec.active: + rec.has_current_revision = True + + @api.depends("all_revision_ids") + def _compute_all_revision_count(self): + for rec in self: + rec.all_revision_count = len(rec.origin_id.all_revision_ids) - 1 + + @api.onchange("directory_id") + def _onchange_directory_id(self): + for rec in self: + if rec.directory_id: + rec.has_versioning = rec.directory_id.has_versioning + + def action_view_revision(self): + self.ensure_one() + action = self.env.ref("dms_version.action_dms_revisions_file") + res = action.read()[0] + res["domain"] = [ + "&", + ("id", "in", self.origin_id.all_revision_ids.ids), + "|", + ("active", "=", False), + ("active", "=", True), + ] + return res + + @api.model + def create(self, values): + res = super().create(values) + if "origin_id" not in values: + res.origin_id = res + return res + + def write(self, vals): + if ( + not self.env.context.get("restore_old_revision", False) + and "active" in vals + and vals["active"] + ): + raise exceptions.UserError( + _("Please use the restore button to activate this revision.") + ) + if vals.get("content"): + versions = self.filtered(lambda x: x.has_versioning) + super(DmsFile, versions).write({"active": False}) + action = versions.with_context(new_vals=vals).create_revision() + res = self.search(action["domain"]) + if versions.ids == self.ids: + return res + else: + return super( + DmsFile, self.filtered(lambda x: not x.has_versioning) + ).write(vals) + res = super().write(vals) + return res + + def _get_new_rev_data(self, new_rev_number): + self.ensure_one() + res = super()._get_new_rev_data(new_rev_number) + new_vals = self.env.context.get("new_vals", False) + if new_vals: + res.update(new_vals) + res["origin_id"] = self.origin_id.id + res["parent_id"] = self.id + res["active"] = True + max_new_rev_number = ( + max(self.origin_id.all_revision_ids.mapped("revision_number")) + 1 + ) + res["revision_number"] = max_new_rev_number + return res + + def action_restore_old_revision(self): + self.ensure_one() + rec_link = self.get_html_link() + msg1 = _("This version is restored.") + if self.current_revision_id.active: + current_version_link = self.current_revision_id.get_html_link() + msg1 += _("\nThe version %s is now archived.") % (current_version_link,) + msg2 = _("The version %s is restored. This version is now archived.") % ( + rec_link, + ) + self.current_revision_id.message_post(body=msg2) + self.message_post(body=msg1) + if self.origin_id.id not in (self.current_revision_id.id, self.id): + msg3 = _("The version %s is restored.") % (rec_link) + if self.current_revision_id.active: + msg3 += _(" The version %s is now archived.") % (current_version_link) + self.origin_id.message_post(body=msg3) + self.current_revision_id.active = False + self.active = True + self.origin_id.all_revision_ids.write({"current_revision_id": self.id}) + self.current_revision_id.write({"current_revision_id": False}) + + def copy_revision_with_context(self): + new_revision = super().copy_revision_with_context() + new_rev_number = new_revision.revision_number + new_revision.write( + { + "name": "%s-%02d" % (self.unrevisioned_name, new_rev_number), + } + ) + return new_revision + + def get_html_link(self): + self.ensure_one() + res = '%s' % ( + self.id, + self.name, + ) + return res + + # (override) function create_revistion() from base_revision + def create_revision(self): + revision_ids = [] + # Looping over records + for rec in self: + # Calling Copy method + copied_rec = rec.copy_revision_with_context() + if hasattr(self, "message_post"): + # (change) add a link for opening the copied record + msg = _("This revision is created from file %s.") % ( + rec.get_html_link() + ) + copied_rec.message_post(body=msg) + msg = _("A new revision %s is created from this file.") % ( + copied_rec.get_html_link() + ) + rec.message_post(body=msg) + if rec.id != rec.origin_id.id: + msg = _("A new revision %s is created from file %s.") % ( + copied_rec.get_html_link(), + rec.get_html_link(), + ) + rec.origin_id.message_post(body=msg) + revision_ids.append(copied_rec.id) + action = { + "type": "ir.actions.act_window", + "view_mode": "tree,form", + "name": _("New Revisions"), + "res_model": self._name, + "domain": [("id", "in", revision_ids)], + "target": "current", + } + return action diff --git a/dms_version/models/dms_storage.py b/dms_version/models/dms_storage.py new file mode 100644 index 000000000..370bbef58 --- /dev/null +++ b/dms_version/models/dms_storage.py @@ -0,0 +1,14 @@ +# Copyright 2017-2020 MuK IT GmbH +# Copyright 2021 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class DmsStorage(models.Model): + _inherit = "dms.storage" + + has_versioning = fields.Boolean( + default=False, + help="Indicates if files have an active version control.", + ) diff --git a/dms_version/readme/CONTRIBUTORS.rst b/dms_version/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..9657f7dc5 --- /dev/null +++ b/dms_version/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Mathias Markl +* `Tecnativa `_:", + + * Víctor Martínez diff --git a/dms_version/readme/DESCRIPTION.rst b/dms_version/readme/DESCRIPTION.rst new file mode 100644 index 000000000..59613e934 --- /dev/null +++ b/dms_version/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Documents Versioning enables version control for the document management system. diff --git a/dms_version/readme/ROADMAP.rst b/dms_version/readme/ROADMAP.rst new file mode 100644 index 000000000..1403b6a76 --- /dev/null +++ b/dms_version/readme/ROADMAP.rst @@ -0,0 +1 @@ +Creating an addon to compress attachments could be useful. diff --git a/dms_version/readme/USAGE.rst b/dms_version/readme/USAGE.rst new file mode 100644 index 000000000..fd6fa198e --- /dev/null +++ b/dms_version/readme/USAGE.rst @@ -0,0 +1 @@ +Go to any file form and if the file has an older version its possible to revert it. diff --git a/dms_version/security/ir.model.access.csv b/dms_version/security/ir.model.access.csv new file mode 100644 index 000000000..0cc9fbbe4 --- /dev/null +++ b/dms_version/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_restore_old_revision,access_restore_old_revision,model_restore_old_revision,base.group_user,1,1,1,1 diff --git a/dms_version/security/security.xml b/dms_version/security/security.xml new file mode 100644 index 000000000..e1738bb98 --- /dev/null +++ b/dms_version/security/security.xml @@ -0,0 +1,6 @@ + + + + Allow Change Versioning + + diff --git a/dms_version/static/description/icon.png b/dms_version/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce8ae7f2db6d923ece9b59e2b63cadff827de963 GIT binary patch literal 9562 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 literal 0 HcmV?d00001 diff --git a/dms_version/static/description/icon.svg b/dms_version/static/description/icon.svg new file mode 100644 index 000000000..fd5ad63c0 --- /dev/null +++ b/dms_version/static/description/icon.svg @@ -0,0 +1 @@ + diff --git a/dms_version/static/description/index.html b/dms_version/static/description/index.html new file mode 100644 index 000000000..8aa44b23a --- /dev/null +++ b/dms_version/static/description/index.html @@ -0,0 +1,434 @@ + + + + + + +Document Management System Version + + + +
+

Document Management System Version

+ + +

Beta License: LGPL-3 OCA/dms Translate me on Weblate Try me on Runbot

+

Documents Versioning enables version control for the document management system.

+

Table of contents

+ +
+

Usage

+

Go to any file form and if the file has an older version its possible to revert it.

+
+
+

Known issues / Roadmap

+

Creating an addon to compress attachments could be useful.

+
+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • MuK IT
  • +
  • Tecnativa
  • +
+
+
+

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

+

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

+
+
+
+ + diff --git a/dms_version/tests/__init__.py b/dms_version/tests/__init__.py new file mode 100644 index 000000000..a2fa51905 --- /dev/null +++ b/dms_version/tests/__init__.py @@ -0,0 +1 @@ +from . import test_dms_file diff --git a/dms_version/tests/test_dms_file.py b/dms_version/tests/test_dms_file.py new file mode 100644 index 000000000..a21566ad7 --- /dev/null +++ b/dms_version/tests/test_dms_file.py @@ -0,0 +1,180 @@ +# Copyright 2017-2020 MuK IT GmbH +# Copyright 2021-2022 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +import logging + +from odoo.exceptions import UserError +from odoo.tests import Form + +from odoo.addons.dms.tests.common import StorageDatabaseBaseCase + +_logger = logging.getLogger(__name__) + + +class TestFileVersion(StorageDatabaseBaseCase): + def setUp(self): + super().setUp() + self.storage.has_versioning = True + self.my_directory = self.create_directory(storage=self.storage) + + def test_01_onchange_events_of_directory(self): + self.assertTrue(self.my_directory.has_versioning) + self.my_sub1_directory = self.create_directory( + storage=self.storage, directory=self.my_directory + ) + self.assertTrue(self.my_sub1_directory.has_versioning) + self.my_directory.has_versioning = False + self.my_sub2_directory = self.create_directory( + storage=self.storage, directory=self.my_directory + ) + self.assertFalse(self.my_sub2_directory.has_versioning) + + def test_02_file_version(self): + self.assertTrue(self.my_directory.has_versioning) + _logger.info("Step 1: Create file_01 and file_02") + file_01 = self.create_file(directory=self.my_directory) + self.assertFalse(file_01.current_revision_id) + self.assertTrue(file_01.active) + self.assertTrue(file_01.has_versioning) + file_01.write({"content": base64.b64encode(b"\xff new_1")}) + self.assertFalse(file_01.active) + self.assertTrue(file_01.current_revision_id.active) + self.assertEqual(file_01.current_revision_id.revision_number, 1) + self.assertEqual(file_01.origin_id, file_01) + file_02 = file_01.current_revision_id + self.assertEqual(file_02.origin_id, file_01) + self.assertEqual(file_02.parent_id, file_01) + + _logger.info("Step 2: Create file_03 and file_04") + file_02.write({"content": base64.b64encode(b"\xff new_2")}) + file_03 = file_01.current_revision_id + file_03.write({"content": base64.b64encode(b"\xff new_3")}) + file_04 = file_01.current_revision_id + self.assertEqual(file_01.all_revision_count, 3) + self.assertIn(file_02, file_01.all_revision_ids) + self.assertIn(file_03, file_01.all_revision_ids) + self.assertIn(file_04, file_01.all_revision_ids) + + logs = """ + # Check values in following table + # id origin_id parent_id current_revision_id version_number active + # file_01 1 1 False 4 0 False + # file_02 2 1 1 4 1 False + # file_03 3 1 2 4 2 False + # file_04 4 1 3 False 3 True + """ + _logger.info(logs) + self.assertEqual(file_01.origin_id, file_01) + self.assertFalse(file_01.parent_id) + self.assertEqual(file_01.current_revision_id, file_04) + self.assertFalse(file_01.active) + self.assertEqual(file_01.revision_number, 0) + self.assertEqual(file_02.origin_id, file_01) + self.assertEqual(file_02.parent_id, file_01) + self.assertEqual(file_02.current_revision_id, file_04) + self.assertFalse(file_02.active) + self.assertEqual(file_02.revision_number, 1) + self.assertEqual(file_03.origin_id, file_01) + self.assertEqual(file_03.parent_id, file_02) + self.assertEqual(file_03.current_revision_id, file_04) + self.assertFalse(file_03.active) + self.assertEqual(file_03.revision_number, 2) + self.assertEqual(file_04.origin_id, file_01) + self.assertEqual(file_04.parent_id, file_03) + self.assertFalse(file_04.current_revision_id) + self.assertTrue(file_04.active) + self.assertEqual(file_04.revision_number, 3) + + _logger.info("Step 3: Restore file_02") + wizard = Form( + self.env["restore.old.revision"].with_context(active_id=file_02.id) + ).save() + wizard.action_done() + logs = """ + # Check values in following table + # id current_revision_id active + # file_01 1 2 False + # file_02 2 False True + # file_03 3 2 False + # file_04 4 2 False + """ + _logger.info(logs) + self.assertEqual(file_01.current_revision_id, file_02) + self.assertFalse(file_02.current_revision_id) + self.assertEqual(file_03.current_revision_id, file_02) + self.assertEqual(file_04.current_revision_id, file_02) + self.assertFalse(file_01.active) + self.assertTrue(file_02.active) + self.assertFalse(file_03.active) + self.assertFalse(file_04.active) + _logger.info("Step 4: Create file_05") + file_02.write({"content": base64.b64encode(b"\xff new_4")}) + file_05 = file_01.current_revision_id + logs = """ + # Check values in following table + # id origin_id parent_id current_revision_id version_number active + # file_01 1 1 False 5 0 False + # file_02 2 1 1 5 1 False + # file_03 3 1 2 5 2 False + # file_04 4 1 3 5 3 False + # file_05 5 1 2 False 4 True + """ + _logger.info(logs) + self.assertEqual(file_01.origin_id, file_01) + self.assertFalse(file_01.parent_id) + self.assertEqual(file_01.current_revision_id, file_05) + self.assertFalse(file_01.active) + self.assertEqual(file_01.revision_number, 0) + self.assertEqual(file_02.origin_id, file_01) + self.assertEqual(file_02.parent_id, file_01) + self.assertEqual(file_02.current_revision_id, file_05) + self.assertFalse(file_02.active) + self.assertEqual(file_02.revision_number, 1) + self.assertEqual(file_03.origin_id, file_01) + self.assertEqual(file_03.parent_id, file_02) + self.assertEqual(file_03.current_revision_id, file_05) + self.assertFalse(file_03.active) + self.assertEqual(file_03.revision_number, 2) + self.assertEqual(file_04.origin_id, file_01) + self.assertEqual(file_04.parent_id, file_03) + self.assertEqual(file_04.current_revision_id, file_05) + self.assertFalse(file_04.active) + self.assertEqual(file_04.revision_number, 3) + self.assertEqual(file_05.origin_id, file_01) + self.assertEqual(file_05.parent_id, file_02) + self.assertFalse(file_05.current_revision_id) + self.assertTrue(file_05.active) + self.assertEqual(file_05.revision_number, 4) + + _logger.info("check_unique_name_revision_number") + error_message = "Reference and revision must be unique in a directory." + with self.assertRaises(UserError, msg=error_message): + file_04.write({"revision_number": 1}) + + _logger.info("check_unique_origin_revision_number") + error_message = 'Revision number "1" must be unique.' + with self.assertRaises(UserError, msg=error_message): + file_04.write({"revision_number": 1}) + + _logger.info("check_set_file_active_with_function_write") + error_message = "Please use the restore button to activate this revision." + with self.assertRaises(UserError, msg=error_message): + file_04.write({"active": True}) + + _logger.info("check_unique_active_file") + error_message = 'Found more active version of file "%s".' % (file_01.name) + with self.assertRaises(UserError, msg=error_message): + file_04.with_context(restore_old_revision=True).write({"active": True}) + + _logger.info("check_action_view_revision") + expected_domain = [ + "&", + ("id", "in", file_01.all_revision_ids.ids), + "|", + ("active", "=", False), + ("active", "=", True), + ] + action = file_05.action_view_revision() + self.assertEqual(action["domain"], expected_domain) diff --git a/dms_version/views/dms_directory_view.xml b/dms_version/views/dms_directory_view.xml new file mode 100644 index 000000000..4ef36461a --- /dev/null +++ b/dms_version/views/dms_directory_view.xml @@ -0,0 +1,27 @@ + + + + + dms_directory.form + dms.directory + + + + + + + + + dms_directory.form + dms.directory + + + + + + + + diff --git a/dms_version/views/dms_file_view.xml b/dms_version/views/dms_file_view.xml new file mode 100644 index 000000000..c8b1ea08b --- /dev/null +++ b/dms_version/views/dms_file_view.xml @@ -0,0 +1,164 @@ + + + + + Revisions + dms.file + tree,form + + + dms_file.search + dms.file + + + + + + + + + + + + + + dms_file.tree + dms.file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {'readonly': [('active', '=', False)]} + + + + + diff --git a/dms_version/views/dms_storage_view.xml b/dms_version/views/dms_storage_view.xml new file mode 100644 index 000000000..a19ac9679 --- /dev/null +++ b/dms_version/views/dms_storage_view.xml @@ -0,0 +1,27 @@ + + + + + dms_storage.form + dms.storage + + + + + + + + + dms_storage.form + dms.storage + + + + + + + + diff --git a/dms_version/wizard/__init__.py b/dms_version/wizard/__init__.py new file mode 100644 index 000000000..6995a99c3 --- /dev/null +++ b/dms_version/wizard/__init__.py @@ -0,0 +1 @@ +from . import restore_old_revision diff --git a/dms_version/wizard/restore_old_revision.py b/dms_version/wizard/restore_old_revision.py new file mode 100644 index 000000000..57d477cfd --- /dev/null +++ b/dms_version/wizard/restore_old_revision.py @@ -0,0 +1,33 @@ +from odoo import api, fields, models + + +class RestoreOldRevision(models.TransientModel): + _name = "restore.old.revision" + _description = "Restore Old Revision" + + file_id = fields.Many2one( + comodel_name="dms.file", + required=True, + string="Old Version", + ) + current_revision_id = fields.Many2one( + comodel_name="dms.file", + string="Current Version", + related="file_id.current_revision_id", + ) + has_current_revision = fields.Boolean( + related="file_id.has_current_revision", + ) + + @api.model + def default_get(self, fields): + res = super().default_get(fields) + active_id = self.env.context.get("active_id") + res["file_id"] = (active_id,) + return res + + def action_done(self): + self.file_id.with_context( + restore_old_revision=True + ).action_restore_old_revision() + return {"type": "ir.actions.act_window_close"} diff --git a/dms_version/wizard/restore_old_revision_view.xml b/dms_version/wizard/restore_old_revision_view.xml new file mode 100644 index 000000000..751e72191 --- /dev/null +++ b/dms_version/wizard/restore_old_revision_view.xml @@ -0,0 +1,57 @@ + + + + + restore.old.revision.form + restore.old.revision + +
+
+ Do you really want to restore this archived file? + Be aware that the current version will be archived if you continue.! +
+
+ Do you really want to restore this archived file? +
+ + + + + +
+
+
+
+
+ + + Restore Old Revision + ir.actions.act_window + restore.old.revision + form + new + + +
+