You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

преди 11 години
преди 13 години
преди 13 години
преди 11 години
преди 11 години
преди 11 години
преди 11 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 14 години
преди 12 години
преди 12 години
преди 11 години
преди 11 години
преди 11 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. # Tree (Hierarchical) Nested Set Model (nsm)
  4. #
  5. # To use the nested set model,
  6. # use the following pattern
  7. # 1. name your parent field as "parent_item_group" if not have a property nsm_parent_field as your field name in the document class
  8. # 2. have a field called "old_parent" in your fields list - this identifies whether the parent has been changed
  9. # 3. call update_nsm(doc_obj) in the on_upate method
  10. # ------------------------------------------
  11. from __future__ import unicode_literals
  12. import webnotes
  13. from webnotes import msgprint, _
  14. class NestedSetRecursionError(webnotes.ValidationError): pass
  15. class NestedSetMultipleRootsError(webnotes.ValidationError): pass
  16. class NestedSetChildExistsError(webnotes.ValidationError): pass
  17. class NestedSetInvalidMergeError(webnotes.ValidationError): pass
  18. # called in the on_update method
  19. def update_nsm(doc_obj):
  20. # get fields, data from the DocType
  21. opf = 'old_parent'
  22. if str(doc_obj.__class__)=='webnotes.model.doc.Document':
  23. # passed as a Document object
  24. d = doc_obj
  25. pf = "parent_" + webnotes.scrub(d.doctype)
  26. else:
  27. # passed as a DocType object
  28. d = doc_obj.doc
  29. pf = "parent_" + webnotes.scrub(d.doctype)
  30. if hasattr(doc_obj,'nsm_parent_field'):
  31. pf = doc_obj.nsm_parent_field
  32. if hasattr(doc_obj,'nsm_oldparent_field'):
  33. opf = doc_obj.nsm_oldparent_field
  34. p, op = d.fields.get(pf) or None, d.fields.get(opf) or None
  35. # has parent changed (?) or parent is None (root)
  36. if not d.lft and not d.rgt:
  37. update_add_node(d, p or '', pf)
  38. elif op != p:
  39. update_move_node(d, pf)
  40. # set old parent
  41. d.fields[opf] = p
  42. webnotes.conn.set_value(d.doctype, d.name, opf, p or '')
  43. # reload
  44. d._loadfromdb()
  45. def update_add_node(doc, parent, parent_field):
  46. """
  47. insert a new node
  48. """
  49. from webnotes.utils import now
  50. n = now()
  51. doctype = doc.doctype
  52. name = doc.name
  53. # get the last sibling of the parent
  54. if parent:
  55. left, right = webnotes.conn.sql("select lft, rgt from `tab%s` where name=%s" \
  56. % (doctype, "%s"), parent)[0]
  57. validate_loop(doc.doctype, doc.name, left, right)
  58. else: # root
  59. right = webnotes.conn.sql("select ifnull(max(rgt),0)+1 from `tab%s` where ifnull(`%s`,'') =''" % (doctype, parent_field))[0][0]
  60. right = right or 1
  61. # update all on the right
  62. webnotes.conn.sql("update `tab%s` set rgt = rgt+2, modified='%s' where rgt >= %s" %(doctype,n,right))
  63. webnotes.conn.sql("update `tab%s` set lft = lft+2, modified='%s' where lft >= %s" %(doctype,n,right))
  64. # update index of new node
  65. if webnotes.conn.sql("select * from `tab%s` where lft=%s or rgt=%s"% (doctype, right, right+1)):
  66. webnotes.msgprint("Nested set error. Please send mail to support")
  67. raise Exception
  68. webnotes.conn.sql("update `tab%s` set lft=%s, rgt=%s, modified='%s' where name='%s'" % (doctype,right,right+1,n,name))
  69. return right
  70. def update_move_node(doc, parent_field):
  71. parent = doc.fields.get(parent_field)
  72. if parent:
  73. new_parent = webnotes.conn.sql("""select lft, rgt from `tab%s`
  74. where name = %s""" % (doc.doctype, '%s'), parent, as_dict=1)[0]
  75. validate_loop(doc.doctype, doc.name, new_parent.lft, new_parent.rgt)
  76. # move to dark side
  77. webnotes.conn.sql("""update `tab%s` set lft = -lft, rgt = -rgt
  78. where lft >= %s and rgt <= %s"""% (doc.doctype, '%s', '%s'), (doc.lft, doc.rgt))
  79. # shift left
  80. diff = doc.rgt - doc.lft + 1
  81. webnotes.conn.sql("""update `tab%s` set lft = lft -%s, rgt = rgt - %s
  82. where lft > %s"""% (doc.doctype, '%s', '%s', '%s'), (diff, diff, doc.rgt))
  83. # shift left rgts of ancestors whose only rgts must shift
  84. webnotes.conn.sql("""update `tab%s` set rgt = rgt - %s
  85. where lft < %s and rgt > %s"""% (doc.doctype, '%s', '%s', '%s'),
  86. (diff, doc.lft, doc.rgt))
  87. if parent:
  88. new_parent = webnotes.conn.sql("""select lft, rgt from `tab%s`
  89. where name = %s""" % (doc.doctype, '%s'), parent, as_dict=1)[0]
  90. # set parent lft, rgt
  91. webnotes.conn.sql("""update `tab%s` set rgt = rgt + %s
  92. where name = %s"""% (doc.doctype, '%s', '%s'), (diff, parent))
  93. # shift right at new parent
  94. webnotes.conn.sql("""update `tab%s` set lft = lft + %s, rgt = rgt + %s
  95. where lft > %s""" % (doc.doctype, '%s', '%s', '%s'),
  96. (diff, diff, new_parent.rgt))
  97. # shift right rgts of ancestors whose only rgts must shift
  98. webnotes.conn.sql("""update `tab%s` set rgt = rgt + %s
  99. where lft < %s and rgt > %s""" % (doc.doctype, '%s', '%s', '%s'),
  100. (diff, new_parent.lft, new_parent.rgt))
  101. new_diff = new_parent.rgt - doc.lft
  102. else:
  103. # new root
  104. max_rgt = webnotes.conn.sql("""select max(rgt) from `tab%s`""" % doc.doctype)[0][0]
  105. new_diff = max_rgt + 1 - doc.lft
  106. # bring back from dark side
  107. webnotes.conn.sql("""update `tab%s` set lft = -lft + %s, rgt = -rgt + %s
  108. where lft < 0"""% (doc.doctype, '%s', '%s'), (new_diff, new_diff))
  109. def rebuild_tree(doctype, parent_field):
  110. """
  111. call rebuild_node for all root nodes
  112. """
  113. # get all roots
  114. webnotes.conn.auto_commit_on_many_writes = 1
  115. right = 1
  116. result = webnotes.conn.sql("SELECT name FROM `tab%s` WHERE `%s`='' or `%s` IS NULL ORDER BY name ASC" % (doctype, parent_field, parent_field))
  117. for r in result:
  118. right = rebuild_node(doctype, r[0], right, parent_field)
  119. webnotes.conn.auto_commit_on_many_writes = 0
  120. def rebuild_node(doctype, parent, left, parent_field):
  121. """
  122. reset lft, rgt and recursive call for all children
  123. """
  124. from webnotes.utils import now
  125. n = now()
  126. # the right value of this node is the left value + 1
  127. right = left+1
  128. # get all children of this node
  129. result = webnotes.conn.sql("SELECT name FROM `tab%s` WHERE `%s`='%s'" % (doctype, parent_field, parent))
  130. for r in result:
  131. right = rebuild_node(doctype, r[0], right, parent_field)
  132. # we've got the left value, and now that we've processed
  133. # the children of this node we also know the right value
  134. webnotes.conn.sql("UPDATE `tab%s` SET lft=%s, rgt=%s, modified='%s' WHERE name='%s'" % (doctype,left,right,n,parent))
  135. #return the right value of this node + 1
  136. return right+1
  137. def validate_loop(doctype, name, lft, rgt):
  138. """check if item not an ancestor (loop)"""
  139. if name in webnotes.conn.sql_list("""select name from `tab%s` where lft <= %s and rgt >= %s""" % (doctype,
  140. "%s", "%s"), (lft, rgt)):
  141. webnotes.throw("""Item cannot be added to its own descendents.""", NestedSetRecursionError)
  142. class DocTypeNestedSet(object):
  143. def on_update(self):
  144. update_nsm(self)
  145. self.validate_ledger()
  146. def on_trash(self):
  147. if not self.nsm_parent_field:
  148. self.nsm_parent_field = webnotes.scrub(self.doc.doctype) + "_parent"
  149. parent = self.doc.fields[self.nsm_parent_field]
  150. if not parent:
  151. msgprint(_("Root ") + self.doc.doctype + _(" cannot be deleted."), raise_exception=1)
  152. # cannot delete non-empty group
  153. has_children = webnotes.conn.sql("""select count(name) from `tab{doctype}`
  154. where `{nsm_parent_field}`=%s""".format(doctype=self.doc.doctype, nsm_parent_field=self.nsm_parent_field),
  155. (self.doc.name,))[0][0]
  156. if has_children:
  157. webnotes.throw("{cannot_delete}. {children_exist}: {name}.".format(
  158. children_exist=_("Children exist for"), name=self.doc.name,
  159. cannot_delete=_("Cannot delete")), NestedSetChildExistsError)
  160. self.doc.fields[self.nsm_parent_field] = ""
  161. update_nsm(self)
  162. def before_rename(self, olddn, newdn, merge=False, group_fname="is_group"):
  163. if merge:
  164. is_group = webnotes.conn.get_value(self.doc.doctype, newdn, group_fname)
  165. if self.doc.fields[group_fname] != is_group:
  166. webnotes.throw(_("""Merging is only possible between Group-to-Group or
  167. Ledger-to-Ledger"""), NestedSetInvalidMergeError)
  168. def after_rename(self, olddn, newdn, merge=False):
  169. if merge:
  170. parent_field = "parent_" + self.doc.doctype.replace(" ", "_").lower()
  171. rebuild_tree(self.doc.doctype, parent_field)
  172. def validate_one_root(self):
  173. if not self.doc.fields[self.nsm_parent_field]:
  174. if webnotes.conn.sql("""select count(*) from `tab%s` where
  175. ifnull(%s, '')=''""" % (self.doc.doctype, self.nsm_parent_field))[0][0] > 1:
  176. webnotes.throw(_("""Multiple root nodes not allowed."""), NestedSetMultipleRootsError)
  177. def validate_ledger(self, group_identifier="is_group"):
  178. if self.doc.fields.get(group_identifier) == "No":
  179. if webnotes.conn.sql("""select name from `tab%s` where %s=%s and docstatus!=2""" %
  180. (self.doc.doctype, self.nsm_parent_field, '%s'), (self.doc.name)):
  181. webnotes.throw(self.doc.doctype + ": " + self.doc.name +
  182. _(" can not be marked as a ledger as it has existing child"))
  183. def get_root_of(doctype):
  184. """Get root element of a DocType with a tree structure"""
  185. result = webnotes.conn.sql_list("""select name from `tab%s`
  186. where lft=1 and rgt=(select max(rgt) from `tab%s` where docstatus < 2)""" %
  187. (doctype, doctype))
  188. return result[0] if result else None
  189. def get_ancestors_of(doctype, name):
  190. """Get ancestor elements of a DocType with a tree structure"""
  191. lft, rgt = webnotes.conn.get_value(doctype, name, ["lft", "rgt"])
  192. result = webnotes.conn.sql_list("""select name from `tab%s`
  193. where lft<%s and rgt>%s order by lft desc""" % (doctype, "%s", "%s"), (lft, rgt))
  194. return result or []