refactor(nestedset): Using qb or db APIs inplace of raw SQLs
This commit is contained in:
parent
e6e9370b59
commit
3358fdf9a9
1 changed files with 65 additions and 54 deletions
|
|
@ -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()
|
||||
|
||||
if frappe.qb.from_(Table).select("*").where((Table.lft == right) | (Table.rgt == right + 1)).run():
|
||||
frappe.throw(_("Nested set error. Please contact the Administrator."))
|
||||
|
||||
# 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
|
||||
|
||||
frappe.db.sql("update `tab{0}` set lft=%s, rgt=%s where name=%s".format(doctype),
|
||||
(right,right+1, name))
|
||||
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()
|
||||
|
||||
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.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.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):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue