From ee0c108b106bf29d2fed8892f4ebe0a803cd139f Mon Sep 17 00:00:00 2001 From: miquelrosell99 <38435442+miquelrosell99@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:25:27 +0200 Subject: [PATCH 1/2] [FIX] document_page: do not copy history on page duplicate When duplicating a document.page, the One2many history_ids field was being copied by default, so the new page inherited all past revisions including their current state. Add copy=False on document.page.history_ids so duplicates start with a clean history. Add copy=False on the approval-specific fields of document.page.history (state, approved_date, approved_uid) so a duplicated change request resets to draft and does not inherit approval metadata. In document_page_approval, override copy() so the duplicated page starts with a single draft change request containing the original content, rather than reusing copied histories or auto-advancing to 'to approve'. Add tests covering both behaviors. --- document_page/models/document_page.py | 1 + document_page/tests/test_document_page.py | 1 + .../models/document_page.py | 18 ++++++++++++ .../models/document_page_history.py | 5 ++-- .../tests/test_document_page_approval.py | 28 +++++++++++++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/document_page/models/document_page.py b/document_page/models/document_page.py index a85c863dbe2..a37c0047ecc 100644 --- a/document_page/models/document_page.py +++ b/document_page/models/document_page.py @@ -63,6 +63,7 @@ class DocumentPage(models.Model): "page_id", "History", readonly=True, + copy=False, ) menu_id = fields.Many2one("ir.ui.menu", "Menu", readonly=True) content_date = fields.Datetime( diff --git a/document_page/tests/test_document_page.py b/document_page/tests/test_document_page.py index e9b2fd7d151..62587933f5b 100644 --- a/document_page/tests/test_document_page.py +++ b/document_page/tests/test_document_page.py @@ -61,3 +61,4 @@ def test_page_copy(self): self.assertEqual(page_copy.content, page.content) self.assertEqual(page_copy.draft_name, "1.0") self.assertEqual(page_copy.draft_summary, "summary") + self.assertFalse(page_copy.history_ids & page.history_ids) diff --git a/document_page_approval/models/document_page.py b/document_page_approval/models/document_page.py index d57cb9e3073..63c123881b5 100644 --- a/document_page_approval/models/document_page.py +++ b/document_page_approval/models/document_page.py @@ -135,6 +135,24 @@ def _create_history(self, vals): res.action_to_approve() return res + def copy(self, default=None): + self.ensure_one() + # Do not trigger _inverse_content during copy; otherwise _create_history + # would move the new record straight to "to approve". Create the initial + # change request as draft so the duplicated page starts in the approval + # workflow. + default = dict(default or {}, content=False) + new_page = super().copy(default=default) + self.env["document.page.history"].create( + { + "page_id": new_page.id, + "name": new_page.draft_name, + "summary": new_page.draft_summary, + "content": self.content, + } + ) + return new_page + def action_changes_pending_approval(self): self.ensure_one() action = self.env["ir.actions.act_window"]._for_xml_id( diff --git a/document_page_approval/models/document_page_history.py b/document_page_approval/models/document_page_history.py index 62499fe1152..107d3bd90d8 100644 --- a/document_page_approval/models/document_page_history.py +++ b/document_page_approval/models/document_page_history.py @@ -25,11 +25,12 @@ class DocumentPageHistory(models.Model): "Status", default="draft", readonly=True, + copy=False, ) - approved_date = fields.Datetime() + approved_date = fields.Datetime(copy=False) - approved_uid = fields.Many2one("res.users", "Approved by") + approved_uid = fields.Many2one("res.users", "Approved by", copy=False) is_approval_required = fields.Boolean( related="page_id.is_approval_required", string="Approval required" diff --git a/document_page_approval/tests/test_document_page_approval.py b/document_page_approval/tests/test_document_page_approval.py index 27cac61b06c..a36dbaba196 100644 --- a/document_page_approval/tests/test_document_page_approval.py +++ b/document_page_approval/tests/test_document_page_approval.py @@ -342,3 +342,31 @@ def test_cache_has_changes_pending_approval(self): # Approver user evaluates it. self.assertTrue(page.with_user(self.user2).has_changes_pending_approval) + + def test_history_copy(self): + """Duplicating a page must not copy history or approval metadata.""" + page = self.page2 + chreq = self.history_obj.with_user(self.user2).create( + { + "page_id": page.id, + "content": "

Version 1

", + "state": "approved", + "approved_date": "2026-07-31 10:00:00", + "approved_uid": self.user2.id, + } + ) + original_history = page.history_ids + page_copy = page.copy() + self.assertFalse(page_copy.history_ids & original_history) + # The duplicated page starts with a single draft change request that + # carries the original content but no approval metadata. + all_copy_hist = self.history_obj.search([("page_id", "=", page_copy.id)]) + self.assertEqual(len(all_copy_hist), 1) + self.assertEqual(all_copy_hist.state, "draft") + self.assertEqual(all_copy_hist.content, page.content) + self.assertFalse(all_copy_hist.approved_date) + self.assertFalse(all_copy_hist.approved_uid) + chreq_copy = chreq.copy() + self.assertEqual(chreq_copy.state, "draft") + self.assertFalse(chreq_copy.approved_date) + self.assertFalse(chreq_copy.approved_uid) From 6a510e4f8e63e9e6049dd882115f8e260c8d00b9 Mon Sep 17 00:00:00 2001 From: miquelrosell99 <38435442+miquelrosell99@users.noreply.github.com> Date: Mon, 2 Nov 2026 08:55:38 +0100 Subject: [PATCH 2/2] [FIX] document_page_approval: keep single draft change request on page copy The base document_page.copy() writes content=self.content in its default, which overrides the content=False the approval copy() tried to pass; the content inverse then created a change request on top of the one created explicitly, leaving the duplicated page with two history records. Let the inverse create a single change request, but keep it in draft when the page requires approval so the copy starts the approval workflow from scratch instead of firing approval requests. When approval is not required the record is auto-approved as usual, so the copy keeps displaying its content. --- .../models/document_page.py | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/document_page_approval/models/document_page.py b/document_page_approval/models/document_page.py index 63c123881b5..be928c834f3 100644 --- a/document_page_approval/models/document_page.py +++ b/document_page_approval/models/document_page.py @@ -132,26 +132,23 @@ def _compute_user_has_drafts(self): def _create_history(self, vals): res = super()._create_history(vals) - res.action_to_approve() + if not ( + self.env.context.get("document_page_copy") and res.is_approval_required + ): + res.action_to_approve() return res def copy(self, default=None): self.ensure_one() - # Do not trigger _inverse_content during copy; otherwise _create_history - # would move the new record straight to "to approve". Create the initial - # change request as draft so the duplicated page starts in the approval - # workflow. - default = dict(default or {}, content=False) - new_page = super().copy(default=default) - self.env["document.page.history"].create( - { - "page_id": new_page.id, - "name": new_page.draft_name, - "summary": new_page.draft_summary, - "content": self.content, - } - ) - return new_page + # Duplicating a page writes its content, whose inverse creates a + # change request through _create_history. When the page requires + # approval, that request must remain in draft so the copy starts + # the approval workflow from scratch instead of firing approval + # requests or inheriting the state of the original history. When + # approval is not required, the record is auto-approved as usual, + # so the duplicated page keeps displaying its content. + self = self.with_context(document_page_copy=True) + return super().copy(default=default) def action_changes_pending_approval(self): self.ensure_one()