Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

230 lignes
6.8 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, cnt = 0):
  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, cnt)
  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. # commit after every 100
  140. cnt += 1
  141. if cnt % 100 == 0:
  142. cnt = 0
  143. webnotes.conn.sql("commit")
  144. webnotes.conn.sql("start transaction")
  145. #return the right value of this node + 1
  146. return right+1
  147. def update_add_node(doctype, name, parent, parent_field):
  148. """
  149. insert a new node
  150. """
  151. from webnotes.utils import now
  152. n = now()
  153. # get the last sibling of the parent
  154. if parent:
  155. right = webnotes.conn.sql("select rgt from `tab%s` where name='%s'" % (doctype, parent))[0][0]
  156. else: # root
  157. right = webnotes.conn.sql("select ifnull(max(rgt),0)+1 from `tab%s` where ifnull(`%s`,'') =''" % (doctype, parent_field))[0][0]
  158. right = right or 1
  159. # update all on the right
  160. webnotes.conn.sql("update `tab%s` set rgt = rgt+2, modified='%s' where rgt >= %s" %(doctype,n,right))
  161. webnotes.conn.sql("update `tab%s` set lft = lft+2, modified='%s' where lft >= %s" %(doctype,n,right))
  162. # update index of new node
  163. if webnotes.conn.sql("select * from `tab%s` where lft=%s or rgt=%s"% (doctype, right, right+1)):
  164. webnotes.msgprint("Nested set error. Please send mail to support")
  165. raise Exception
  166. webnotes.conn.sql("update `tab%s` set lft=%s, rgt=%s, modified='%s' where name='%s'" % (doctype,right,right+1,n,name))
  167. return right
  168. def update_remove_node(doctype, name):
  169. """
  170. remove a node
  171. """
  172. from webnotes.utils import now
  173. n = now()
  174. left = webnotes.conn.sql("select lft from `tab%s` where name='%s'" % (doctype,name))
  175. if left[0][0]:
  176. # reset this node
  177. webnotes.conn.sql("update `tab%s` set lft=0, rgt=0, modified='%s' where name='%s'" % (doctype,n,name))
  178. # update all on the right
  179. webnotes.conn.sql("update `tab%s` set rgt = rgt-2, modified='%s' where rgt > %s" %(doctype,n,left[0][0]))
  180. webnotes.conn.sql("update `tab%s` set lft = lft-2, modified='%s' where lft > %s" %(doctype,n,left[0][0]))