From aade0df7bf14900613b89307ac3c2a4b76ac5f19 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:16:53 +0100 Subject: [PATCH] feat: new REST API for copy_doc (#31160) * feat: new REST API for copy_doc * test: copy endpoint --- frappe/api/v2.py | 12 ++++++++++++ frappe/tests/test_api_v2.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/frappe/api/v2.py b/frappe/api/v2.py index d003be89f9..13253e7599 100644 --- a/frappe/api/v2.py +++ b/frappe/api/v2.py @@ -95,6 +95,17 @@ def create_doc(doctype: str): return frappe.new_doc(doctype, **data).insert() +def copy_doc(doctype: str, name: str, ignore_no_copy: bool = True): + """Return a clean copy of the given document that can be modified and posted as a new document.""" + doc = frappe.get_doc(doctype, name) + doc.check_permission("read") + doc.apply_fieldlevel_read_permissions() + + copy = frappe.copy_doc(doc, ignore_no_copy=ignore_no_copy) + + return copy.as_dict(no_private_properties=True, no_nulls=True) + + def update_doc(doctype: str, name: str): data = frappe.form_dict @@ -186,6 +197,7 @@ url_rules = [ Rule("/document/", methods=["GET"], endpoint=document_list), Rule("/document/", methods=["POST"], endpoint=create_doc), Rule("/document///", methods=["GET"], endpoint=read_doc), + Rule("/document///copy", methods=["GET"], endpoint=copy_doc), Rule("/document///", methods=["PATCH", "PUT"], endpoint=update_doc), Rule("/document///", methods=["DELETE"], endpoint=delete_doc), Rule( diff --git a/frappe/tests/test_api_v2.py b/frappe/tests/test_api_v2.py index 4ad0480074..6cee357a03 100644 --- a/frappe/tests/test_api_v2.py +++ b/frappe/tests/test_api_v2.py @@ -84,6 +84,25 @@ class TestResourceAPIV2(FrappeAPITestCase): self.assertIsInstance(docname, str) self.GENERATED_DOCUMENTS.append(docname) + def test_copy_document(self): + doc = frappe.get_doc(self.DOCTYPE, self.GENERATED_DOCUMENTS[0]) + + response = self.get(self.resource(self.DOCTYPE, doc.name, "copy")) + self.assertEqual(response.status_code, 200) + data = response.json["data"] + + self.assertEqual(data["doctype"], self.DOCTYPE) + self.assertEqual(data["description"], doc.description) + self.assertEqual(data["status"], doc.status) + self.assertEqual(data["priority"], doc.priority) + + self.assertNotIn("name", data) + self.assertNotIn("creation", data) + self.assertNotIn("modified", data) + self.assertNotIn("modified_by", data) + self.assertNotIn("owner", data) + self.assertNotIn("docstatus", data) + def test_delete_document(self): doc_to_delete = choice(self.GENERATED_DOCUMENTS) response = self.delete(self.resource(self.DOCTYPE, doc_to_delete))