From 10b855ea8702baade556edf6aa3318916e4505f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Geelen=20=28sge=29?= Date: Wed, 20 Sep 2023 10:58:56 +0200 Subject: [PATCH] [ADD] web_editor: add field html history Create a new model mixins that allow other model to activate the history feature on html fields. This history automatically track the changes on the related html field and allow the user to revert to a previous version at any time. This also introduce a new component that can be used to : * List the recent history * See a previous version of the document * Compare a previous version to the current one * Revert the document to a previous version of it task-3039787 closes odoo/odoo#112957 Related: odoo/enterprise#37211 Signed-off-by: David Monjoie (dmo) --- addons/test_html_field_history/__init__.py | 4 + .../test_html_field_history/__manifest__.py | 13 + .../models/__init__.py | 4 + .../models/model_html_field_history_test.py | 19 ++ .../security/ir.model.access.csv | 2 + .../test_html_field_history/tests/__init__.py | 4 + .../tests/test_model.py | 101 +++++++ addons/web_editor/__manifest__.py | 1 + addons/web_editor/models/__init__.py | 1 + addons/web_editor/models/diff_utils.py | 273 ++++++++++++++++++ .../models/html_field_history_mixin.py | 140 +++++++++ .../history_dialog/history_dialog.js | 97 +++++++ .../history_dialog/history_dialog.scss | 51 ++++ .../history_dialog/history_dialog.xml | 53 ++++ .../static/src/js/wysiwyg/wysiwyg.js | 5 + addons/web_editor/tests/__init__.py | 1 + addons/web_editor/tests/test_diff_utils.py | 129 +++++++++ 17 files changed, 898 insertions(+) create mode 100644 addons/test_html_field_history/__init__.py create mode 100644 addons/test_html_field_history/__manifest__.py create mode 100644 addons/test_html_field_history/models/__init__.py create mode 100644 addons/test_html_field_history/models/model_html_field_history_test.py create mode 100644 addons/test_html_field_history/security/ir.model.access.csv create mode 100644 addons/test_html_field_history/tests/__init__.py create mode 100644 addons/test_html_field_history/tests/test_model.py create mode 100644 addons/web_editor/models/diff_utils.py create mode 100644 addons/web_editor/models/html_field_history_mixin.py create mode 100644 addons/web_editor/static/src/components/history_dialog/history_dialog.js create mode 100644 addons/web_editor/static/src/components/history_dialog/history_dialog.scss create mode 100644 addons/web_editor/static/src/components/history_dialog/history_dialog.xml create mode 100644 addons/web_editor/tests/test_diff_utils.py diff --git a/addons/test_html_field_history/__init__.py b/addons/test_html_field_history/__init__.py new file mode 100644 index 00000000000..dc5e6b693d1 --- /dev/null +++ b/addons/test_html_field_history/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/test_html_field_history/__manifest__.py b/addons/test_html_field_history/__manifest__.py new file mode 100644 index 00000000000..c87e7f03eb0 --- /dev/null +++ b/addons/test_html_field_history/__manifest__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Test - html_field_history', + 'version': '1.0', + 'category': 'Hidden', + 'depends': ['web_editor'], + 'data': [ + 'security/ir.model.access.csv', + ], + 'license': 'LGPL-3', +} diff --git a/addons/test_html_field_history/models/__init__.py b/addons/test_html_field_history/models/__init__.py new file mode 100644 index 00000000000..a3b6f86312f --- /dev/null +++ b/addons/test_html_field_history/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import model_html_field_history_test diff --git a/addons/test_html_field_history/models/model_html_field_history_test.py b/addons/test_html_field_history/models/model_html_field_history_test.py new file mode 100644 index 00000000000..266347fab4f --- /dev/null +++ b/addons/test_html_field_history/models/model_html_field_history_test.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ModelHtmlFieldHistoryTest(models.Model): + _description = "Test html_field_history Model" + _name = "html.field.history.test" + _inherit = ["html.field.history.mixin"] + + def _get_versioned_fields(self): + return [ + ModelHtmlFieldHistoryTest.versioned_field_1.name, + ModelHtmlFieldHistoryTest.versioned_field_2.name, + ] + + versioned_field_1 = fields.Html(string="vf1") + versioned_field_2 = fields.Html(string="vf2", sanitize=False) diff --git a/addons/test_html_field_history/security/ir.model.access.csv b/addons/test_html_field_history/security/ir.model.access.csv new file mode 100644 index 00000000000..e45ce3bc479 --- /dev/null +++ b/addons/test_html_field_history/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_resource_test_all,resource.test.all,model_html_field_history_test,base.group_user,1,1,1,1 diff --git a/addons/test_html_field_history/tests/__init__.py b/addons/test_html_field_history/tests/__init__.py new file mode 100644 index 00000000000..e32480285d2 --- /dev/null +++ b/addons/test_html_field_history/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_model diff --git a/addons/test_html_field_history/tests/test_model.py b/addons/test_html_field_history/tests/test_model.py new file mode 100644 index 00000000000..7228fbe4c72 --- /dev/null +++ b/addons/test_html_field_history/tests/test_model.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests.common import TransactionCase, tagged +from odoo.exceptions import ValidationError + + +@tagged("-at_install", "post_install") +class TestModel(TransactionCase): + def setUp(self): + self.env["html.field.history.test"].search([]).unlink() + super().setUp() + + def test_html_field_history_write(self): + rec1 = self.env["html.field.history.test"].create( + { + "versioned_field_1": "mock content", + } + ) + self.assertFalse( + rec1.html_field_history, + "Record creation should not generate revisions", + ) + self.assertFalse( + rec1.html_field_history_metadata, + "We should never have metadata without revisions", + ) + + rec1.write( + { + "versioned_field_1": "mock content 2", + } + ) + self.assertEqual(len(rec1.html_field_history["versioned_field_1"]), 1) + self.assertEqual(len(rec1.html_field_history_metadata["versioned_field_1"]), 1) + self.assertFalse(rec1.html_field_history["versioned_field_2"]) + self.assertFalse(rec1.html_field_history_metadata["versioned_field_2"]) + + rec1.write( + { + "versioned_field_1": "mock content 3", + } + ) + rec1.write( + { + "versioned_field_1": None, + } + ) + self.assertEqual(len(rec1.html_field_history["versioned_field_1"]), 3) + rec1.unlink() + + rec2 = self.env["html.field.history.test"].create( + { + "versioned_field_2": "mock content", + } + ) + self.assertFalse( + rec2.html_field_history, + "Record creation should not generate revisions", + ) + self.assertFalse( + rec2.html_field_history_metadata, + "We should never have metadata without revisions", + ) + + with self.assertRaises( + ValidationError, + msg="We should not be able to versioned a field that is not declared as sanitize=True", + ): + rec2.write( + { + "versioned_field_2": "mock content 2", + } + ) + + rec2.unlink() + + def test_html_field_history_revision_are_sanitized(self): + rec1 = self.env["html.field.history.test"].create( + { + "versioned_field_1": "mock content", + } + ) + self.assertFalse( + rec1.html_field_history, + "Record creation should not generate revisions", + ) + # Attempt to write unsecure HTML inside sanitized html field + rec1.write({"versioned_field_1": 'scam