Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

224 Zeilen
6.6 KiB

  1. # Tree (Hierarchical) Nested Set Model (nsm)
  2. #
  3. # To use the nested set model,
  4. # use the following pattern
  5. # 1. name your parent field as "parent_node" if not have a property nsm_parent_field as your field name in the document class
  6. # 2. have a field called "old_parent" in your fields list - this identifies whether the parent has been changed
  7. # 3. call update_nsm(doc_obj) in the on_upate method
  8. # ------------------------------------------
  9. import webnotes, unittest
  10. class TestNSM(unittest.TestCase):
  11. def setUp(self):
  12. from webnotes.model.doc import Document
  13. self.root = Document(fielddata={'doctype':'nsmtest', 'name':'T001', 'parent':None})
  14. self.first_child = Document(fielddata={'doctype':'nsmtest', 'name':'C001', 'parent_node':'T001'})
  15. self.first_sibling = Document(fielddata={'doctype':'nsmtest', 'name':'C002', 'parent_node':'T001'})
  16. self.grand_child = Document(fielddata={'doctype':'nsmtest', 'name':'GC001', 'parent_node':'C001'})
  17. webnotes.conn.sql("""
  18. create table `tabnsmtest` (
  19. name varchar(120) not null primary key,
  20. creation datetime,
  21. modified datetime,
  22. modified_by varchar(40),
  23. owner varchar(40),
  24. docstatus int(1) default '0',
  25. parent varchar(120),
  26. parentfield varchar(120),
  27. parenttype varchar(120),
  28. idx int(8),
  29. parent_node varchar(180),
  30. old_parent varchar(180),
  31. lft int,
  32. rgt int) ENGINE=InnoDB""")
  33. def test_root(self):
  34. self.root.save(1)
  35. update_nsm(self.root)
  36. self.assertTrue(self.root.lft==1)
  37. self.assertTrue(self.root.rgt==2)
  38. def test_first_child(self):
  39. self.root.save(1)
  40. update_nsm(self.root)
  41. self.first_child.save(1)
  42. update_nsm(self.first_child)
  43. self.root._loadfromdb()
  44. self.assertTrue(self.root.lft==1)
  45. self.assertTrue(self.first_child.lft==2)
  46. self.assertTrue(self.first_child.rgt==3)
  47. self.assertTrue(self.root.rgt==4)
  48. def test_sibling(self):
  49. self.test_first_child()
  50. self.first_sibling.save(1)
  51. update_nsm(self.first_sibling)
  52. self.root._loadfromdb()
  53. self.first_child._loadfromdb()
  54. self.assertTrue(self.root.lft==1)
  55. self.assertTrue(self.first_child.lft==2)
  56. self.assertTrue(self.first_child.rgt==3)
  57. self.assertTrue(self.first_sibling.lft==4)
  58. self.assertTrue(self.first_sibling.rgt==5)
  59. self.assertTrue(self.root.rgt==6)
  60. def test_remove_sibling(self):
  61. self.test_sibling()
  62. self.first_sibling.parent_node = ''
  63. update_nsm(self.first_sibling)
  64. self.root._loadfromdb()
  65. self.first_child._loadfromdb()
  66. self.assertTrue(self.root.lft==1)
  67. self.assertTrue(self.first_child.lft==2)
  68. self.assertTrue(self.first_child.rgt==3)
  69. self.assertTrue(self.root.rgt==4)
  70. self.assertTrue(self.first_sibling.lft==5)
  71. self.assertTrue(self.first_sibling.rgt==6)
  72. def test_change_parent(self):
  73. self.test_sibling()
  74. # add grand child
  75. self.grand_child.save(1)
  76. update_nsm(self.grand_child)
  77. # check lft rgt
  78. self.assertTrue(self.grand_child.lft==3)
  79. self.assertTrue(self.grand_child.rgt==4)
  80. # change parent
  81. self.grand_child.parent_node = 'C002'
  82. self.grand_child.save()
  83. # update
  84. update_nsm(self.grand_child)
  85. # check lft rgt
  86. self.assertTrue(self.grand_child.lft==5)
  87. self.assertTrue(self.grand_child.rgt==6)
  88. def tearDown(self):
  89. webnotes.conn.sql("drop table tabnsmtest")
  90. # called in the on_update method
  91. def update_nsm(doc_obj):
  92. # get fields, data from the DocType
  93. pf, opf = 'parent_node', 'old_parent'
  94. if str(doc_obj.__class__)=='webnotes.model.doc.Document':
  95. # passed as a Document object
  96. d = doc_obj
  97. else:
  98. # passed as a DocType object
  99. d = doc_obj.doc
  100. if hasattr(doc_obj,'nsm_parent_field'):
  101. pf = doc_obj.nsm_parent_field
  102. if hasattr(doc_obj,'nsm_oldparent_field'):
  103. opf = doc_obj.nsm_oldparent_field
  104. p, op = d.fields.get(pf, ''), d.fields.get(opf, '')
  105. # has parent changed (?) or parent is None (root)
  106. if not d.lft and not d.rgt:
  107. update_add_node(d.doctype, d.name, p or '', pf)
  108. elif op != p:
  109. update_remove_node(d.doctype, d.name)
  110. update_add_node(d.doctype, d.name, p or '', pf)
  111. # set old parent
  112. webnotes.conn.set(d, opf, p or '')
  113. # reload
  114. d._loadfromdb()
  115. def rebuild_tree(doctype, parent_field):
  116. """
  117. call rebuild_node for all root nodes
  118. """
  119. # get all roots
  120. right = 1
  121. result = webnotes.conn.sql("SELECT name FROM `tab%s` WHERE `%s`='' or `%s` IS NULL" % (doctype, parent_field, parent_field))
  122. for r in result:
  123. right = rebuild_node(doctype, r[0], right, parent_field)
  124. def rebuild_node(doctype, parent, left, parent_field):
  125. """
  126. reset lft, rgt and recursive call for all children
  127. """
  128. from webnotes.utils import now
  129. n = now()
  130. # the right value of this node is the left value + 1
  131. right = left+1
  132. # get all children of this node
  133. result = webnotes.conn.sql("SELECT name FROM `tab%s` WHERE `%s`='%s'" % (doctype, parent_field, parent))
  134. for r in result:
  135. right = rebuild_node(doctype, r[0], right, parent_field)
  136. # we've got the left value, and now that we've processed
  137. # the children of this node we also know the right value
  138. webnotes.conn.sql("UPDATE `tab%s` SET lft=%s, rgt=%s, modified='%s' WHERE name='%s'" % (doctype,left,right,n,parent))
  139. #return the right value of this node + 1
  140. return right+1
  141. def update_add_node(doctype, name, parent, parent_field):
  142. """
  143. insert a new node
  144. """
  145. from webnotes.utils import now
  146. n = now()
  147. # get the last sibling of the parent
  148. if parent:
  149. right = webnotes.conn.sql("select rgt from `tab%s` where name='%s'" % (doctype, parent))[0][0]
  150. else: # root
  151. right = webnotes.conn.sql("select ifnull(max(rgt),0)+1 from `tab%s` where ifnull(`%s`,'') =''" % (doctype, parent_field))[0][0]
  152. right = right or 1
  153. # update all on the right
  154. webnotes.conn.sql("update `tab%s` set rgt = rgt+2, modified='%s' where rgt >= %s" %(doctype,n,right))
  155. webnotes.conn.sql("update `tab%s` set lft = lft+2, modified='%s' where lft >= %s" %(doctype,n,right))
  156. # update index of new node
  157. if webnotes.conn.sql("select * from `tab%s` where lft=%s or rgt=%s"% (doctype, right, right+1)):
  158. webnotes.msgprint("Nested set error. Please send mail to support")
  159. raise Exception
  160. webnotes.conn.sql("update `tab%s` set lft=%s, rgt=%s, modified='%s' where name='%s'" % (doctype,right,right+1,n,name))
  161. return right
  162. def update_remove_node(doctype, name):
  163. """
  164. remove a node
  165. """
  166. from webnotes.utils import now
  167. n = now()
  168. left = webnotes.conn.sql("select lft from `tab%s` where name='%s'" % (doctype,name))
  169. if left[0][0]:
  170. # reset this node
  171. webnotes.conn.sql("update `tab%s` set lft=0, rgt=0, modified='%s' where name='%s'" % (doctype,n,name))
  172. # update all on the right
  173. webnotes.conn.sql("update `tab%s` set rgt = rgt-2, modified='%s' where rgt > %s" %(doctype,n,left[0][0]))
  174. webnotes.conn.sql("update `tab%s` set lft = lft-2, modified='%s' where lft > %s" %(doctype,n,left[0][0]))