Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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