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.6 KiB

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