|
|
@@ -16,6 +16,9 @@ import frappe |
|
|
|
from frappe import _ |
|
|
|
from frappe.model.document import Document |
|
|
|
from frappe.query_builder import DocType, Order |
|
|
|
from frappe.query_builder.functions import Coalesce, Max |
|
|
|
from frappe.query_builder.utils import DocType |
|
|
|
|
|
|
|
|
|
|
|
class NestedSetRecursionError(frappe.ValidationError): pass |
|
|
|
class NestedSetMultipleRootsError(frappe.ValidationError): pass |
|
|
@@ -51,87 +54,91 @@ def update_add_node(doc, parent, parent_field): |
|
|
|
""" |
|
|
|
insert a new node |
|
|
|
""" |
|
|
|
|
|
|
|
doctype = doc.doctype |
|
|
|
name = doc.name |
|
|
|
Table = DocType(doctype) |
|
|
|
|
|
|
|
# get the last sibling of the parent |
|
|
|
if parent: |
|
|
|
left, right = frappe.db.sql("select lft, rgt from `tab{0}` where name=%s for update" |
|
|
|
.format(doctype), parent)[0] |
|
|
|
left, right = frappe.db.get_value(doctype, {"name": parent}, ["lft", "rgt"], for_update=True) |
|
|
|
validate_loop(doc.doctype, doc.name, left, right) |
|
|
|
else: # root |
|
|
|
right = frappe.db.sql(""" |
|
|
|
SELECT COALESCE(MAX(rgt), 0) + 1 FROM `tab{0}` |
|
|
|
WHERE COALESCE(`{1}`, '') = '' |
|
|
|
""".format(doctype, parent_field))[0][0] |
|
|
|
|
|
|
|
right = frappe.qb.from_(Table).select( |
|
|
|
Coalesce(Max(Table.rgt), 0) |
|
|
|
).where(Coalesce(Table[parent_field], "") == "").run(pluck=True)[0] |
|
|
|
|
|
|
|
right = right or 1 |
|
|
|
|
|
|
|
# update all on the right |
|
|
|
frappe.db.sql("update `tab{0}` set rgt = rgt+2 where rgt >= %s" |
|
|
|
.format(doctype), (right,)) |
|
|
|
frappe.db.sql("update `tab{0}` set lft = lft+2 where lft >= %s" |
|
|
|
.format(doctype), (right,)) |
|
|
|
frappe.qb.update(Table).set(Table.rgt, Table.rgt + 2).where(Table.rgt >= right).run() |
|
|
|
frappe.qb.update(Table).set(Table.lft, Table.lft + 2).where(Table.lft >= right).run() |
|
|
|
|
|
|
|
# update index of new node |
|
|
|
if frappe.db.sql("select * from `tab{0}` where lft=%s or rgt=%s".format(doctype), (right, right+1)): |
|
|
|
frappe.msgprint(_("Nested set error. Please contact the Administrator.")) |
|
|
|
raise Exception |
|
|
|
if frappe.qb.from_(Table).select("*").where((Table.lft == right) | (Table.rgt == right + 1)).run(): |
|
|
|
frappe.throw(_("Nested set error. Please contact the Administrator.")) |
|
|
|
|
|
|
|
frappe.db.sql("update `tab{0}` set lft=%s, rgt=%s where name=%s".format(doctype), |
|
|
|
(right,right+1, name)) |
|
|
|
# update index of new node |
|
|
|
frappe.qb.update(Table).set(Table.lft, right).set(Table.rgt, right + 1).where(Table.name == name).run() |
|
|
|
return right |
|
|
|
|
|
|
|
|
|
|
|
def update_move_node(doc, parent_field): |
|
|
|
parent = doc.get(parent_field) |
|
|
|
def update_move_node(doc: Document, parent_field: str): |
|
|
|
parent: str = doc.get(parent_field) |
|
|
|
Table = DocType(doc.doctype) |
|
|
|
|
|
|
|
if parent: |
|
|
|
new_parent = frappe.db.sql("""select lft, rgt from `tab{0}` |
|
|
|
where name = %s for update""".format(doc.doctype), parent, as_dict=1)[0] |
|
|
|
new_parent = frappe.qb.from_(Table).select( |
|
|
|
Table.lft, Table.rgt |
|
|
|
).where(Table.name == parent).for_update().run(as_dict=True)[0] |
|
|
|
|
|
|
|
validate_loop(doc.doctype, doc.name, new_parent.lft, new_parent.rgt) |
|
|
|
|
|
|
|
# move to dark side |
|
|
|
frappe.db.sql("""update `tab{0}` set lft = -lft, rgt = -rgt |
|
|
|
where lft >= %s and rgt <= %s""".format(doc.doctype), (doc.lft, doc.rgt)) |
|
|
|
frappe.qb.update(Table).set(Table.lft, - Table.lft).set(Table.rgt, - Table.rgt).where( |
|
|
|
(Table.lft >= doc.lft) & (Table.rgt <= doc.rgt) |
|
|
|
).run() |
|
|
|
|
|
|
|
# shift left |
|
|
|
diff = doc.rgt - doc.lft + 1 |
|
|
|
frappe.db.sql("""update `tab{0}` set lft = lft -%s, rgt = rgt - %s |
|
|
|
where lft > %s""".format(doc.doctype), (diff, diff, doc.rgt)) |
|
|
|
frappe.qb.update(Table).set(Table.lft, Table.lft - diff).set(Table.rgt, Table.rgt - diff).where( |
|
|
|
Table.lft > doc.rgt |
|
|
|
).run() |
|
|
|
|
|
|
|
# shift left rgts of ancestors whose only rgts must shift |
|
|
|
frappe.db.sql("""update `tab{0}` set rgt = rgt - %s |
|
|
|
where lft < %s and rgt > %s""".format(doc.doctype), (diff, doc.lft, doc.rgt)) |
|
|
|
frappe.qb.update(Table).set(Table.rgt, Table.rgt - diff).where( |
|
|
|
(Table.lft < doc.lft) & (Table.rgt > doc.rgt) |
|
|
|
).run() |
|
|
|
|
|
|
|
if parent: |
|
|
|
new_parent = frappe.db.sql("""select lft, rgt from `tab%s` |
|
|
|
where name = %s for update""" % (doc.doctype, '%s'), parent, as_dict=1)[0] |
|
|
|
|
|
|
|
# set parent lft, rgt |
|
|
|
frappe.db.sql("""update `tab{0}` set rgt = rgt + %s |
|
|
|
where name = %s""".format(doc.doctype), (diff, parent)) |
|
|
|
frappe.qb.update(Table).set(Table.rgt, Table.rgt + diff).where(Table.name == parent).run() |
|
|
|
|
|
|
|
# shift right at new parent |
|
|
|
frappe.db.sql("""update `tab{0}` set lft = lft + %s, rgt = rgt + %s |
|
|
|
where lft > %s""".format(doc.doctype), (diff, diff, new_parent.rgt)) |
|
|
|
frappe.qb.update(Table).set(Table.lft, Table.lft + diff).set(Table.rgt, Table.rgt + diff).where( |
|
|
|
(Table.lft >= new_parent.lft) & (Table.lft <= new_parent.rgt) |
|
|
|
).run() |
|
|
|
|
|
|
|
# shift right rgts of ancestors whose only rgts must shift |
|
|
|
frappe.db.sql("""update `tab{0}` set rgt = rgt + %s |
|
|
|
where lft < %s and rgt > %s""".format(doc.doctype), |
|
|
|
(diff, new_parent.lft, new_parent.rgt)) |
|
|
|
frappe.qb.update(Table).set(Table.lft, Table.lft + diff).set(Table.rgt, Table.rgt + diff).where( |
|
|
|
Table.lft > new_parent.rgt |
|
|
|
).run() |
|
|
|
|
|
|
|
# shift right rgts of ancestors whose only rgts must shift |
|
|
|
frappe.qb.update(Table).set(Table.rgt, Table.rgt + diff).where( |
|
|
|
(Table.lft < new_parent.lft) & (Table.rgt > new_parent.rgt) |
|
|
|
).run() |
|
|
|
|
|
|
|
new_diff = new_parent.rgt - doc.lft |
|
|
|
else: |
|
|
|
# new root |
|
|
|
max_rgt = frappe.db.sql("""select max(rgt) from `tab{0}`""".format(doc.doctype))[0][0] |
|
|
|
max_rgt = frappe.qb.from_(Table).select(Max(Table.rgt)).run(pluck=True)[0] |
|
|
|
new_diff = max_rgt + 1 - doc.lft |
|
|
|
|
|
|
|
# bring back from dark side |
|
|
|
frappe.db.sql("""update `tab{0}` set lft = -lft + %s, rgt = -rgt + %s |
|
|
|
where lft < 0""".format(doc.doctype), (new_diff, new_diff)) |
|
|
|
frappe.qb.update(Table).set( |
|
|
|
Table.lft, -Table.lft + new_diff |
|
|
|
).set( |
|
|
|
Table.rgt, -Table.rgt + new_diff |
|
|
|
).where(Table.lft < 0).run() |
|
|
|
|
|
|
|
|
|
|
|
@frappe.whitelist() |
|
|
@@ -197,10 +204,10 @@ def rebuild_node(doctype, parent, left, parent_field): |
|
|
|
|
|
|
|
def validate_loop(doctype, name, lft, rgt): |
|
|
|
"""check if item not an ancestor (loop)""" |
|
|
|
if name in frappe.db.sql_list("""select name from `tab{0}` where lft <= %s and rgt >= %s""" |
|
|
|
.format(doctype), (lft, rgt)): |
|
|
|
if name in frappe.get_all(doctype, filters={"lft": ["<=", lft], "rgt": [">=", rgt]}, pluck="name"): |
|
|
|
frappe.throw(_("Item cannot be added to its own descendents"), NestedSetRecursionError) |
|
|
|
|
|
|
|
|
|
|
|
class NestedSet(Document): |
|
|
|
def __setup__(self): |
|
|
|
if self.meta.get("nsm_parent_field"): |
|
|
@@ -232,9 +239,7 @@ class NestedSet(Document): |
|
|
|
raise |
|
|
|
|
|
|
|
def validate_if_child_exists(self): |
|
|
|
has_children = frappe.db.sql("""select count(name) from `tab{doctype}` |
|
|
|
where `{nsm_parent_field}`=%s""".format(doctype=self.doctype, nsm_parent_field=self.nsm_parent_field), |
|
|
|
(self.name,))[0][0] |
|
|
|
has_children = frappe.db.count(self.doctype, filters={self.nsm_parent_field: self.name}) |
|
|
|
if has_children: |
|
|
|
frappe.throw(_("Cannot delete {0} as it has child nodes").format(self.name), NestedSetChildExistsError) |
|
|
|
|
|
|
@@ -251,8 +256,7 @@ class NestedSet(Document): |
|
|
|
parent_field = self.nsm_parent_field |
|
|
|
|
|
|
|
# set old_parent for children |
|
|
|
frappe.db.sql("update `tab{0}` set old_parent=%s where {1}=%s" |
|
|
|
.format(self.doctype, parent_field), (newdn, newdn)) |
|
|
|
frappe.db.set_value(self.doctype, {"old_parent": newdn}, {parent_field: newdn}, update_modified=False, for_update=False) |
|
|
|
|
|
|
|
if merge: |
|
|
|
rebuild_tree(self.doctype, parent_field) |
|
|
@@ -269,8 +273,7 @@ class NestedSet(Document): |
|
|
|
|
|
|
|
def validate_ledger(self, group_identifier="is_group"): |
|
|
|
if hasattr(self, group_identifier) and not bool(self.get(group_identifier)): |
|
|
|
if frappe.db.sql("""select name from `tab{0}` where {1}=%s and docstatus!=2""" |
|
|
|
.format(self.doctype, self.nsm_parent_field), (self.name)): |
|
|
|
if frappe.get_all(self.doctype, {self.nsm_parent_field: self.name, "docstatus": ("!=", 2)}): |
|
|
|
frappe.throw(_("{0} {1} cannot be a leaf node as it has children").format(_(self.doctype), self.name)) |
|
|
|
|
|
|
|
def get_ancestors(self): |
|
|
@@ -291,10 +294,18 @@ class NestedSet(Document): |
|
|
|
|
|
|
|
def get_root_of(doctype): |
|
|
|
"""Get root element of a DocType with a tree structure""" |
|
|
|
result = frappe.db.sql("""select t1.name from `tab{0}` t1 where |
|
|
|
(select count(*) from `tab{1}` t2 where |
|
|
|
t2.lft < t1.lft and t2.rgt > t1.rgt) = 0 |
|
|
|
and t1.rgt > t1.lft""".format(doctype, doctype)) |
|
|
|
from frappe.query_builder.functions import Count |
|
|
|
Table = DocType(doctype) |
|
|
|
t1 = Table.as_("t1") |
|
|
|
t2 = Table.as_("t2") |
|
|
|
|
|
|
|
subq = frappe.qb.from_(t2).select(Count("*")).where( |
|
|
|
(t2.lft < t1.lft) & (t2.rgt > t1.rgt) |
|
|
|
) |
|
|
|
result = frappe.qb.from_(t1).select(t1.name).where( |
|
|
|
(subq == 0) & (t1.rgt > t1.lft) # depends on https://github.com/frappe/frappe/pull/16107 |
|
|
|
).run() |
|
|
|
|
|
|
|
return result[0][0] if result else None |
|
|
|
|
|
|
|
def get_ancestors_of(doctype, name, order_by="lft desc", limit=None): |
|
|
|