From 10da1622f460354e0eb90aac5bfe7fd419389399 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 19 Jul 2022 21:12:21 +0530 Subject: [PATCH 01/10] fix: form view broken for virtual doctype --- frappe/core/doctype/test/test.py | 4 ++-- frappe/desk/form/load.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py index 664d06ac84..c3b8f6accf 100644 --- a/frappe/core/doctype/test/test.py +++ b/frappe/core/doctype/test/test.py @@ -3,7 +3,7 @@ import json -# import frappe +import frappe from frappe.model.document import Document @@ -25,7 +25,7 @@ class test(Document): def get_list(self, args): with open("data_file.json") as read_file: - return [json.load(read_file)] + return [frappe._dict(json.load(read_file))] def get_value(self, fields, filters, **kwargs): # return [] diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 898c6461e9..f63796d061 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -31,7 +31,8 @@ def getdoc(doctype, name, user=None): name = doctype if not frappe.db.exists(doctype, name): - return [] + if not frappe.get_meta(doctype).is_virtual: + return [] doc = frappe.get_doc(doctype, name) run_onload(doc) From b2203893a1e47566cf26618d24c445e0d2528751 Mon Sep 17 00:00:00 2001 From: Shridhar Date: Fri, 22 Jul 2022 10:59:59 +0530 Subject: [PATCH 02/10] fix: do not fetch comments from parent for virtual doctype --- frappe/core/doctype/comment/comment.py | 6 +++++- frappe/desk/reportview.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index dab9cfbfe4..cf00651b15 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -11,6 +11,7 @@ from frappe.desk.doctype.notification_log.notification_log import ( get_title, get_title_html, ) +from frappe.desk.reportview import is_virtual_doctype from frappe.exceptions import ImplicitCommitError from frappe.model.document import Document from frappe.utils import get_fullname @@ -152,7 +153,10 @@ def get_comments_from_parent(doc): `_comments` """ try: - _comments = frappe.db.get_value(doc.reference_doctype, doc.reference_name, "_comments") or "[]" + if is_virtual_doctype(doc.reference_doctype): + _comments = "[]" + else: + _comments = frappe.db.get_value(doc.reference_doctype, doc.reference_name, "_comments") or "[]" except Exception as e: if frappe.db.is_missing_table_or_column(e): diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index c163c4fab5..68dc2933f0 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -272,7 +272,7 @@ def compress(data, args=None): values.append(new_row) # add user info for assignments (avatar) - if row._assign: + if row.get("_assign", ""): for user in json.loads(row._assign): add_user_info(user, user_info) From e88bc35cae766527825a608468b87928798383c5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Jul 2022 12:19:27 +0530 Subject: [PATCH 03/10] refactor: move is_virtual_doctype to relevant file --- frappe/core/doctype/comment/comment.py | 2 +- frappe/desk/form/load.py | 6 +++--- frappe/desk/reportview.py | 7 +------ frappe/model/utils/__init__.py | 6 ++++++ 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index cf00651b15..4cd15dc815 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -179,7 +179,7 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments): not reference_doctype or not reference_name or frappe.db.get_value("DocType", reference_doctype, "issingle") - or frappe.db.get_value("DocType", reference_doctype, "is_virtual") + or is_virtual_doctype(reference_doctype) ): return diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index f63796d061..167fab4a07 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -11,6 +11,7 @@ import frappe.share import frappe.utils from frappe import _, _dict from frappe.desk.form.document_follow import is_document_followed +from frappe.model.utils import is_virtual_doctype from frappe.model.utils.user_settings import get_user_settings from frappe.permissions import get_doc_permissions from frappe.utils.data import cstr @@ -30,9 +31,8 @@ def getdoc(doctype, name, user=None): if not name: name = doctype - if not frappe.db.exists(doctype, name): - if not frappe.get_meta(doctype).is_virtual: - return [] + if not frappe.db.exists(doctype, name) and not is_virtual_doctype(doctype): + return [] doc = frappe.get_doc(doctype, name) run_onload(doc) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 68dc2933f0..9500023ffc 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -13,8 +13,8 @@ from frappe.core.doctype.access_log.access_log import make_access_log from frappe.model import child_table_fields, default_fields, optional_fields from frappe.model.base_document import get_controller from frappe.model.db_query import DatabaseQuery +from frappe.model.utils import is_virtual_doctype from frappe.utils import add_user_info, cstr, format_duration -from frappe.utils.caching import site_cache @frappe.whitelist() @@ -731,8 +731,3 @@ def get_filters_cond( else: cond = "" return cond - - -@site_cache(maxsize=128) -def is_virtual_doctype(doctype): - return frappe.db.get_value("DocType", doctype, "is_virtual") diff --git a/frappe/model/utils/__init__.py b/frappe/model/utils/__init__.py index 351b19c8eb..2e940decb6 100644 --- a/frappe/model/utils/__init__.py +++ b/frappe/model/utils/__init__.py @@ -7,6 +7,7 @@ import frappe from frappe import _ from frappe.build import html_to_js_template from frappe.utils import cstr +from frappe.utils.caching import site_cache STANDARD_FIELD_CONVERSION_MAP = { "name": "Link", @@ -99,3 +100,8 @@ def get_fetch_values(doctype, fieldname, value): out[df.fieldname] = frappe.db.get_value(link_df.options, value, source_fieldname) return out + + +@site_cache(maxsize=128) +def is_virtual_doctype(doctype): + return frappe.db.get_value("DocType", doctype, "is_virtual") From b8d56eaefbc8539129748489142d9009100aeae0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Jul 2022 12:31:10 +0530 Subject: [PATCH 04/10] chore: remove test doctypes from prod --- frappe/core/doctype/test/test.json | 4 +- .../doctype/test_rename_new/__init__.py | 0 .../test_rename_new/test_rename_new.json | 42 ------------------- .../test_rename_new/test_rename_new.py | 9 ---- .../test_rename_new/test_test_rename_new.py | 8 ---- 5 files changed, 3 insertions(+), 60 deletions(-) delete mode 100644 frappe/custom/doctype/test_rename_new/__init__.py delete mode 100644 frappe/custom/doctype/test_rename_new/test_rename_new.json delete mode 100644 frappe/custom/doctype/test_rename_new/test_rename_new.py delete mode 100644 frappe/custom/doctype/test_rename_new/test_test_rename_new.py diff --git a/frappe/core/doctype/test/test.json b/frappe/core/doctype/test/test.json index 31a57c9964..4187984d2b 100644 --- a/frappe/core/doctype/test/test.json +++ b/frappe/core/doctype/test/test.json @@ -17,7 +17,7 @@ "index_web_pages_for_search": 1, "is_virtual": 1, "links": [], - "modified": "2021-03-31 10:06:57.919697", + "modified": "2022-07-22 03:00:59.560061", "modified_by": "Administrator", "module": "Core", "name": "test", @@ -36,7 +36,9 @@ "write": 1 } ], + "read_only": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/frappe/custom/doctype/test_rename_new/__init__.py b/frappe/custom/doctype/test_rename_new/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/custom/doctype/test_rename_new/test_rename_new.json b/frappe/custom/doctype/test_rename_new/test_rename_new.json deleted file mode 100644 index 0b089091a1..0000000000 --- a/frappe/custom/doctype/test_rename_new/test_rename_new.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "actions": [], - "creation": "2021-01-13 12:47:03.572640", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "random" - ], - "fields": [ - { - "fieldname": "random", - "fieldtype": "Data", - "label": "random" - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-01-13 12:47:03.572640", - "modified_by": "Administrator", - "module": "Custom", - "name": "Test rename new", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "route": "test-rename", - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/custom/doctype/test_rename_new/test_rename_new.py b/frappe/custom/doctype/test_rename_new/test_rename_new.py deleted file mode 100644 index ed89d1fad1..0000000000 --- a/frappe/custom/doctype/test_rename_new/test_rename_new.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies and contributors -# License: MIT. See LICENSE - -# import frappe -from frappe.model.document import Document - - -class Testrenamenew(Document): - pass diff --git a/frappe/custom/doctype/test_rename_new/test_test_rename_new.py b/frappe/custom/doctype/test_rename_new/test_test_rename_new.py deleted file mode 100644 index f1ccf42ede..0000000000 --- a/frappe/custom/doctype/test_rename_new/test_test_rename_new.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies and Contributors -# License: MIT. See LICENSE -# import frappe -import unittest - - -class Testrenamenew(unittest.TestCase): - pass From e8efd64dbc0cca53b11042ab39ca771f8c2ecc26 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Jul 2022 13:03:29 +0530 Subject: [PATCH 05/10] refactor!: better API contracts for virtual doctype Current APIs implement class methods as instance method, which is problamamtic while implementing methods. E.g. If load_from_db doesn't like empty docname then all class method will stop working. This change while breaking is essential for usability of virtual doctype. --- frappe/core/doctype/test/test.py | 22 +++++++-------- frappe/desk/reportview.py | 8 +++--- frappe/model/virtual_doctype.py | 46 ++++++++++++++++++++++++++++++++ frappe/modules/utils.py | 13 +++++---- 4 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 frappe/model/virtual_doctype.py diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py index c3b8f6accf..e17b3a0a4a 100644 --- a/frappe/core/doctype/test/test.py +++ b/frappe/core/doctype/test/test.py @@ -8,7 +8,7 @@ from frappe.model.document import Document class test(Document): - def db_insert(self): + def db_insert(self, *args, **kwargs): d = self.get_valid_dict(convert_dates_to_str=True) with open("data_file.json", "w+") as read_file: json.dump(d, read_file) @@ -18,26 +18,22 @@ class test(Document): d = json.load(read_file) super(Document, self).__init__(d) - def db_update(self): + def db_update(self, *args, **kwargs): d = self.get_valid_dict(convert_dates_to_str=True) with open("data_file.json", "w+") as read_file: json.dump(d, read_file) - def get_list(self, args): + @staticmethod + def get_list(args): with open("data_file.json") as read_file: return [frappe._dict(json.load(read_file))] - def get_value(self, fields, filters, **kwargs): - # return [] - with open("data_file.json") as read_file: - return [json.load(read_file)] + @staticmethod + def get_count(args): + return 5 - def get_count(self, args): - # return [] - with open("data_file.json") as read_file: - return [json.load(read_file)] - - def get_stats(self, args): + @staticmethod + def get_stats(args): # return [] with open("data_file.json") as read_file: return [json.load(read_file)] diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 9500023ffc..679b052baf 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -24,7 +24,7 @@ def get(): # If virtual doctype get data from controller het_list method if is_virtual_doctype(args.doctype): controller = get_controller(args.doctype) - data = compress(controller(args.doctype).get_list(args)) + data = compress(controller.get_list(args)) else: data = compress(execute(**args), args=args) return data @@ -37,7 +37,7 @@ def get_list(): if is_virtual_doctype(args.doctype): controller = get_controller(args.doctype) - data = controller(args.doctype).get_list(args) + data = controller.get_list(args) else: # uncompressed (refactored from frappe.model.db_query.get_list) data = execute(**args) @@ -52,7 +52,7 @@ def get_count(): if is_virtual_doctype(args.doctype): controller = get_controller(args.doctype) - data = controller(args.doctype).get_count(args) + data = controller.get_count(args) else: distinct = "distinct " if args.distinct == "true" else "" args.fields = [f"count({distinct}`tab{args.doctype}`.name) as total_count"] @@ -528,7 +528,7 @@ def get_sidebar_stats(stats, doctype, filters=None): if is_virtual_doctype(doctype): controller = get_controller(doctype) args = {"stats": stats, "filters": filters} - data = controller(doctype).get_stats(args) + data = controller.get_stats(args) else: data = get_stats(stats, doctype, filters) diff --git a/frappe/model/virtual_doctype.py b/frappe/model/virtual_doctype.py new file mode 100644 index 0000000000..a21d88e605 --- /dev/null +++ b/frappe/model/virtual_doctype.py @@ -0,0 +1,46 @@ +from typing import Protocol + + +class VirtualDoctype(Protocol): + """This class documents requirements that must be met by a doctype controller to function as virtual doctype + + + Additional requirements: + - DocType controller has to inherit from `frappe.model.document.Document` class + + Note: + - "Backend" here means any storage service, it can be a database, flat file or network call to API. + """ + + # ============ class/static methods ============ + + @staticmethod + def get_list(args): + """Similar to reportview.get_list""" + ... + + @staticmethod + def get_count(args) -> int: + """Similar to reportview.get_count, return total count of documents on listview.""" + ... + + @staticmethod + def get_stats(args): + """Similar to reportview.get_stats, return sidebar stats.""" + ... + + # ============ instance methods ============ + + def db_insert(self, *args, **kwargs) -> None: + """Serialize the `Document` object and insert it in backend.""" + ... + + def load_from_db(self) -> None: + """Using self.name initialize current document from backend data. + + This is responsible for updatinng __dict__ of class with all the fields on doctype.""" + ... + + def db_update(self, *args, **kwargs): + """Serialize the `Document` object and update existing document in backend.""" + ... diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index f4a386cfc9..a87ac1b3db 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -296,22 +296,25 @@ def make_boilerplate(template, doc, opts=None): custom_controller = "pass" if doc.get("is_virtual"): custom_controller = """ - def db_insert(self): + def db_insert(self, *args, **kwargs): pass def load_from_db(self): pass - def db_update(self): + def db_update(self, *args, **kwargs): pass - def get_list(self, args): + @staticmethod + def get_list(args): pass - def get_count(self, args): + @staticmethod + def get_count(args): pass - def get_stats(self, args): + @staticmethod + def get_stats(args): pass""" with open(target_file_path, "w") as target: From f1d638473f4461bfbfda559e753aa1424e310d7a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Jul 2022 14:50:13 +0530 Subject: [PATCH 06/10] refactor: add reload function back Assigning a function to a different name breaks inheritance model. E.g. doc.reload() won't call virtual doctype's load_from_db but call original load_from_db --- frappe/model/document.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/model/document.py b/frappe/model/document.py index 3bddaa9aae..cadfa573d0 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -179,7 +179,9 @@ class Document(BaseDocument): if hasattr(self, "__setup__"): self.__setup__() - reload = load_from_db + def reload(self): + """Reload document from database""" + self.load_from_db() def get_latest(self): if not getattr(self, "latest", None): From 4c877258f0cf512f530de140a0d9481b1870821f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Jul 2022 14:57:32 +0530 Subject: [PATCH 07/10] test: add tests for virtual doctype desk interactions --- frappe/core/doctype/test/test.py | 63 ++++++++++++++++++------ frappe/core/doctype/test/test_test.py | 70 ++++++++++++++++++++++++--- frappe/model/virtual_doctype.py | 10 +++- 3 files changed, 121 insertions(+), 22 deletions(-) diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py index e17b3a0a4a..ffb4becb10 100644 --- a/frappe/core/doctype/test/test.py +++ b/frappe/core/doctype/test/test.py @@ -1,39 +1,74 @@ # Copyright (c) 2021, Frappe Technologies and contributors # License: MIT. See LICENSE +""" This is a virtual doctype controller for test/demo purposes. + +- It uses a JSON file on disk as "backend". +- All docs are stored in "docs" key of JSON file. +""" import json +import os +from typing import Any, TypedDict import frappe from frappe.model.document import Document +DATA_FILE = "data_file.json" + + +class Data(TypedDict): + docs: dict[str, dict[str, Any]] + + +def get_current_data() -> Data: + """Read data from disk""" + if not os.path.exists(DATA_FILE): + return {"docs": {}} + + with open(DATA_FILE) as f: + return json.load(f) + + +def update_data(data: Data) -> None: + """Flush updated data to disk""" + with open(DATA_FILE, "w+") as data_file: + json.dump(data, data_file) + class test(Document): def db_insert(self, *args, **kwargs): d = self.get_valid_dict(convert_dates_to_str=True) - with open("data_file.json", "w+") as read_file: - json.dump(d, read_file) + + data = get_current_data() + data["docs"][d.name] = d + + update_data(data) def load_from_db(self): - with open("data_file.json") as read_file: - d = json.load(read_file) - super(Document, self).__init__(d) + data = get_current_data() + d = data["docs"].get(self.name) + super(Document, self).__init__(d) def db_update(self, *args, **kwargs): - d = self.get_valid_dict(convert_dates_to_str=True) - with open("data_file.json", "w+") as read_file: - json.dump(d, read_file) + # For this example insert and update are same operation, + # it might be different for you + self.db_insert(*args, **kwargs) + + def delete(self): + data = get_current_data() + data["docs"].pop(self.name, None) + update_data(data) @staticmethod def get_list(args): - with open("data_file.json") as read_file: - return [frappe._dict(json.load(read_file))] + data = get_current_data() + return [frappe._dict(doc) for name, doc in data["docs"].items()] @staticmethod def get_count(args): - return 5 + data = get_current_data() + return len(data["docs"]) @staticmethod def get_stats(args): - # return [] - with open("data_file.json") as read_file: - return [json.load(read_file)] + return {} diff --git a/frappe/core/doctype/test/test_test.py b/frappe/core/doctype/test/test_test.py index 6080c200c1..08bb8b2b84 100644 --- a/frappe/core/doctype/test/test_test.py +++ b/frappe/core/doctype/test/test_test.py @@ -1,8 +1,66 @@ -# Copyright (c) 2021, Frappe Technologies and Contributors -# License: MIT. See LICENSE -# import frappe -import unittest +import json +import os + +import frappe +from frappe.core.doctype.test.test import DATA_FILE +from frappe.core.doctype.test.test import test as VirtDocType +from frappe.desk.form.save import savedocs +from frappe.tests.utils import FrappeTestCase -class Testtest(unittest.TestCase): - pass +class Testtest(FrappeTestCase): + def tearDown(self): + if os.path.exists(DATA_FILE): + os.remove(DATA_FILE) + + def test_insert_update_and_load_from_desk(self): + """Insert, update, reload and assert changes""" + + frappe.response.docs = [] + doc = json.dumps( + { + "docstatus": 0, + "doctype": "test", + "name": "new-test-1", + "__islocal": 1, + "__unsaved": 1, + "owner": "Administrator", + "test": "Original Data", + } + ) + savedocs(doc, "Save") + + docname = frappe.response.docs[0]["name"] + + doc = frappe.get_doc("test", docname) + doc.test = "New Data" + + savedocs(doc.as_json(), "Save") + + doc.reload() + self.assertEqual(doc.test, "New Data") + + def test_multiple_doc_insert_and_get_list(self): + doc1 = frappe.get_doc(doctype="test", test="first").insert() + doc2 = frappe.get_doc(doctype="test", test="second").insert() + + docs = {doc1.name, doc2.name} + + doc2.reload() + doc1.reload() + updated_docs = {doc1.name, doc2.name} + self.assertEqual(docs, updated_docs) + + listed_docs = {d.name for d in VirtDocType.get_list({})} + self.assertEqual(docs, listed_docs) + + def test_get_count(self): + args = {"doctype": "test", "filters": [], "fields": []} + self.assertIsInstance(VirtDocType.get_count(args), int) + + def test_delete_doc(self): + doc = frappe.get_doc(doctype="test", test="data").insert() + doc.delete() + + listed_docs = {d.name for d in VirtDocType.get_list({})} + self.assertNotIn(doc.name, listed_docs) diff --git a/frappe/model/virtual_doctype.py b/frappe/model/virtual_doctype.py index a21d88e605..dc228f9577 100644 --- a/frappe/model/virtual_doctype.py +++ b/frappe/model/virtual_doctype.py @@ -1,5 +1,7 @@ from typing import Protocol +import frappe + class VirtualDoctype(Protocol): """This class documents requirements that must be met by a doctype controller to function as virtual doctype @@ -15,7 +17,7 @@ class VirtualDoctype(Protocol): # ============ class/static methods ============ @staticmethod - def get_list(args): + def get_list(args) -> list[frappe._dict]: """Similar to reportview.get_list""" ... @@ -41,6 +43,10 @@ class VirtualDoctype(Protocol): This is responsible for updatinng __dict__ of class with all the fields on doctype.""" ... - def db_update(self, *args, **kwargs): + def db_update(self, *args, **kwargs) -> None: """Serialize the `Document` object and update existing document in backend.""" ... + + def delete(self, *args, **kwargs) -> None: + """Delete the current document from backend""" + ... From d83712d553e313682bcd1043dc320a85418fc9cd Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Jul 2022 15:42:04 +0530 Subject: [PATCH 08/10] feat: delete support for virtual doctypes from desk --- frappe/core/doctype/test/test_test.py | 3 ++- frappe/model/delete_doc.py | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/test/test_test.py b/frappe/core/doctype/test/test_test.py index 08bb8b2b84..70ba1fd1e1 100644 --- a/frappe/core/doctype/test/test_test.py +++ b/frappe/core/doctype/test/test_test.py @@ -60,7 +60,8 @@ class Testtest(FrappeTestCase): def test_delete_doc(self): doc = frappe.get_doc(doctype="test", test="data").insert() - doc.delete() + + frappe.delete_doc(doc.doctype, doc.name) listed_docs = {d.name for d in VirtDocType.get_list({})} self.assertNotIn(doc.name, listed_docs) diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index b555dfc5dc..332a4337e2 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -11,6 +11,7 @@ from frappe import _, get_module_path from frappe.desk.doctype.tag.tag import delete_tags_for_document from frappe.model.dynamic_links import get_dynamic_link_map from frappe.model.naming import revert_series_if_last +from frappe.model.utils import is_virtual_doctype from frappe.utils.file_manager import remove_all from frappe.utils.global_search import delete_for_document from frappe.utils.password import delete_all_passwords_for @@ -57,11 +58,16 @@ def delete_doc( doctype = frappe.form_dict.get("dt") name = frappe.form_dict.get("dn") + is_virtual = is_virtual_doctype(doctype) + names = name if isinstance(name, str) or isinstance(name, int): names = [name] for name in names or []: + if is_virtual: + frappe.get_doc(doctype, name).delete() + continue # already deleted..? if not frappe.db.exists(doctype, name): From e35671203c4cb35813bb3511b22cea89c3044a14 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Jul 2022 16:15:16 +0530 Subject: [PATCH 09/10] refactor: simplify virtual doctype example --- frappe/core/doctype/test/test.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py index ffb4becb10..0219fd10ab 100644 --- a/frappe/core/doctype/test/test.py +++ b/frappe/core/doctype/test/test.py @@ -4,11 +4,16 @@ """ This is a virtual doctype controller for test/demo purposes. - It uses a JSON file on disk as "backend". -- All docs are stored in "docs" key of JSON file. +- Key is docname and value is the document itself. + +Example: +{ + "doc1": {"name": "doc1", ...} + "doc2": {"name": "doc2", ...} +} """ import json import os -from typing import Any, TypedDict import frappe from frappe.model.document import Document @@ -16,20 +21,16 @@ from frappe.model.document import Document DATA_FILE = "data_file.json" -class Data(TypedDict): - docs: dict[str, dict[str, Any]] - - -def get_current_data() -> Data: +def get_current_data() -> dict[str, dict]: """Read data from disk""" if not os.path.exists(DATA_FILE): - return {"docs": {}} + return {} with open(DATA_FILE) as f: return json.load(f) -def update_data(data: Data) -> None: +def update_data(data: dict[str, dict]) -> None: """Flush updated data to disk""" with open(DATA_FILE, "w+") as data_file: json.dump(data, data_file) @@ -40,13 +41,13 @@ class test(Document): d = self.get_valid_dict(convert_dates_to_str=True) data = get_current_data() - data["docs"][d.name] = d + data[d.name] = d update_data(data) def load_from_db(self): data = get_current_data() - d = data["docs"].get(self.name) + d = data.get(self.name) super(Document, self).__init__(d) def db_update(self, *args, **kwargs): @@ -56,18 +57,18 @@ class test(Document): def delete(self): data = get_current_data() - data["docs"].pop(self.name, None) + data.pop(self.name, None) update_data(data) @staticmethod def get_list(args): data = get_current_data() - return [frappe._dict(doc) for name, doc in data["docs"].items()] + return [frappe._dict(doc) for name, doc in data.items()] @staticmethod def get_count(args): data = get_current_data() - return len(data["docs"]) + return len(data) @staticmethod def get_stats(args): From 0f83e9e944686353a1ff6f34c5914581de007e8d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Jul 2022 16:24:02 +0530 Subject: [PATCH 10/10] fix: correct import path Co-authored-by: gavin --- frappe/core/doctype/comment/comment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index 4cd15dc815..625933791d 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -11,7 +11,7 @@ from frappe.desk.doctype.notification_log.notification_log import ( get_title, get_title_html, ) -from frappe.desk.reportview import is_virtual_doctype +from frappe.model.utils import is_virtual_doctype from frappe.exceptions import ImplicitCommitError from frappe.model.document import Document from frappe.utils import get_fullname