feat: Create NestedSet doctypes via configuration

This commit is contained in:
Faris Ansari 2019-09-02 15:58:52 +05:30
parent e2b3c56902
commit bff94703cf
7 changed files with 94 additions and 8 deletions

View file

@ -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

View file

@ -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
}
}

View file

@ -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`"""

View file

@ -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("-", "")

View file

@ -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)
))

View file

@ -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");
}

View file

@ -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()