Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

nestedset.py 7.4 KiB

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