diff --git a/frappe/core/doctype/doctype/boilerplate/controller._py b/frappe/core/doctype/doctype/boilerplate/controller._py index 65f9b2bc35..97e23c0037 100644 --- a/frappe/core/doctype/doctype/boilerplate/controller._py +++ b/frappe/core/doctype/doctype/boilerplate/controller._py @@ -4,7 +4,7 @@ from __future__ import unicode_literals # import frappe -from frappe.model.document import Document +{base_class_import} -class {classname}(Document): +class {classname}({base_class}): pass diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index a2e5c21202..238404ae1f 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -12,6 +12,7 @@ "is_submittable", "istable", "issingle", + "is_tree", "editable_grid", "quick_entry", "cb01", @@ -30,6 +31,7 @@ "form_settings_section", "image_field", "timeline_field", + "nsm_parent_field", "max_attachments", "column_break_23", "hide_toolbar", @@ -438,11 +440,23 @@ "fieldtype": "Select", "label": "Database Engine", "options": "InnoDB\nMyISAM" + }, + { + "default": "0", + "description": "Tree structures are implemented using Nested Set", + "fieldname": "is_tree", + "fieldtype": "Check", + "label": "Is Tree" + }, + { + "fieldname": "nsm_parent_field", + "fieldtype": "Data", + "label": "Parent Field (Tree)" } ], "icon": "fa fa-bolt", "idx": 6, - "modified": "2019-07-04 23:23:17.174960", + "modified": "2019-09-02 05:51:29.411525", "modified_by": "Administrator", "module": "Core", "name": "DocType", @@ -475,4 +489,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 6bb2953c92..e808cd3053 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -84,6 +84,7 @@ class DocType(Document): self.make_amendable() self.make_repeatable() + self.validate_nestedset() self.validate_website() if not self.is_new(): @@ -581,6 +582,64 @@ class DocType(Document): df = dict(fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', options='Auto Repeat', insert_after=insert_after, read_only=1, no_copy=1, print_hide=1) create_custom_field(self.name, df) + def validate_nestedset(self): + if not self.is_tree: + return + self.add_nestedset_fields() + # set field as mandatory + field = self.meta.get_field('nsm_parent_field') + field.reqd = 1 + # check if field is valid + fieldnames = [df.fieldname for df in self.fields] + if self.nsm_parent_field and self.nsm_parent_field not in fieldnames: + frappe.throw(_("Parent Field must be a valid fieldname"), InvalidFieldNameError) + + def add_nestedset_fields(self): + """If is_tree is set, add parent_field, lft, rgt, is_group fields.""" + fieldnames = [df.fieldname for df in self.fields] + if 'lft' in fieldnames: + return + + self.append("fields", { + "label": "Left", + "fieldtype": "Int", + "fieldname": "lft", + "read_only": 1, + "hidden": 1, + "no_copy": 1 + }) + + self.append("fields", { + "label": "Right", + "fieldtype": "Int", + "fieldname": "rgt", + "read_only": 1, + "hidden": 1, + "no_copy": 1 + }) + + self.append("fields", { + "label": "Is Group", + "fieldtype": "Check", + "fieldname": "is_group" + }) + self.append("fields", { + "label": "Old Parent", + "fieldtype": "Link", + "options": self.name, + "fieldname": "old_parent" + }) + + parent_field_label = "Parent {}".format(self.name) + parent_field_name = frappe.scrub(parent_field_label) + self.append("fields", { + "label": parent_field_label, + "fieldtype": "Link", + "options": self.name, + "fieldname": parent_field_name + }) + self.nsm_parent_field = parent_field_name + def get_max_idx(self): """Returns the highest `idx`""" diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index df34ea491d..7296f03f8f 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -30,14 +30,15 @@ def get_controller(doctype): :param doctype: DocType name as string.""" from frappe.model.document import Document + from frappe.utils.nestedset import NestedSet global _classes if not doctype in _classes: - module_name, custom = frappe.db.get_value("DocType", doctype, ("module", "custom"), cache=True) \ - or ["Core", False] + module_name, custom, is_tree = frappe.db.get_value("DocType", doctype, ("module", "custom", "is_tree"), cache=True) \ + or ["Core", False, False] if custom: - _class = Document + _class = NestedSet if is_tree else Document else: module = load_doctype_module(doctype, module_name) classname = doctype.replace(" ", "").replace("-", "") diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index ab45689dda..b06297023e 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -240,6 +240,12 @@ def make_boilerplate(template, doc, opts=None): if not opts: opts = {} + base_class = 'Document' + base_class_import = 'from frappe.model.document import Document' + if doc.is_tree: + base_class = 'NestedSet' + base_class_import = 'from frappe.utils.nestedset import NestedSet' + with open(target_file_path, 'w') as target: with open(os.path.join(get_module_path("core"), "doctype", scrub(doc.doctype), "boilerplate", template), 'r') as source: @@ -248,5 +254,7 @@ def make_boilerplate(template, doc, opts=None): app_publisher=app_publisher, year=frappe.utils.nowdate()[:4], classname=doc.name.replace(" ", ""), + base_class_import=base_class_import, + base_class=base_class, doctype=doc.name, **opts) )) diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index 45f23611b4..a43d92591d 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -62,7 +62,7 @@ frappe.views.ListSidebar = class ListSidebar { show_list_link = true; } - if (frappe.treeview_settings[this.doctype]) { + if (frappe.treeview_settings[this.doctype] || frappe.get_meta(this.doctype).is_tree) { this.sidebar.find(".tree-link").removeClass("hide"); } diff --git a/frappe/utils/nestedset.py b/frappe/utils/nestedset.py index 87be93b42a..dbc43de00b 100644 --- a/frappe/utils/nestedset.py +++ b/frappe/utils/nestedset.py @@ -183,6 +183,10 @@ def validate_loop(doctype, name, lft, rgt): frappe.throw(_("Item cannot be added to its own descendents"), NestedSetRecursionError) class NestedSet(Document): + def __setup__(self): + if self.meta.nsm_parent_field: + self.nsm_parent_field = self.meta.nsm_parent_field + def on_update(self): update_nsm(self) self.validate_ledger()