/controllers/plugin_mptt.py

https://github.com/bliving/sqlabs · Python · 395 lines · 202 code · 48 blank · 145 comment · 12 complexity · 7254f3a8e14503b865c9994e3634aff7 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. from plugin_mptt import MPTTModel
  3. import unittest
  4. import re
  5. import datetime
  6. if request.function == 'test':
  7. db = DAL('sqlite:memory:')
  8. ### setup core objects #########################################################
  9. mptt = MPTTModel(db)
  10. mptt.settings.table_node_name = 'plugin_mptt_node'
  11. mptt.settings.extra_fields = {
  12. 'plugin_mptt_node':
  13. [Field('name'),
  14. Field('created_on', 'datetime', default=request.now)],
  15. }
  16. ### define tables ##############################################################'
  17. mptt.define_tables()
  18. table_node = mptt.settings.table_node
  19. NodeParent = table_node.with_alias('node_parent')
  20. parent_left = NodeParent.on(NodeParent.id==table_node.parent)
  21. ### populate records ###########################################################
  22. deleted = db(table_node.created_on<request.now-datetime.timedelta(minutes=30)).delete()
  23. if deleted:
  24. table_node.truncate()
  25. session.flash = 'the database has been refreshed'
  26. redirect(URL('index'))
  27. ### demo functions #############################################################
  28. def index():
  29. return dict(unit_tests=[A('test all', _href=URL('test')),
  30. A('test reading', _href=URL('test', args='reading')),
  31. A('test reparenting', _href=URL('test', args='reparenting')),
  32. A('test deletion', _href=URL('test', args='deletion'))])
  33. ### unit tests #################################################################
  34. class TreeTestMixin():
  35. leading_whitespace_re = re.compile(r'^\s+', re.MULTILINE)
  36. def asserTree(self, nodes, tree_text):
  37. self.assertEqual(
  38. '\n'.join(["%s %s %s %s %s %s" %
  39. (node.name, node.parent and node.parent.name,
  40. node.tree_id, node.level, node.left, node.right)
  41. for node in nodes]),
  42. self.leading_whitespace_re.sub('',tree_text))
  43. def get_all_nodes(self):
  44. return db(table_node.id > 0).select(left=parent_left, orderby=table_node.tree_id)
  45. def get_node(self, node_id):
  46. return db(table_node.id == node_id).select().first()
  47. def build_tree1(self):
  48. self.node1 = mptt.insert_node(target_id=None, name='node1')
  49. self.node2 = mptt.insert_node(target_id=self.node1, name='node2')
  50. self.node3 = mptt.insert_node(target_id=self.node2, name='node3')
  51. self.node4 = mptt.insert_node(target_id=self.node2, name='node4')
  52. self.node5 = mptt.insert_node(target_id=self.node2, name='node5')
  53. self.node6 = mptt.insert_node(target_id=self.node1, name='node6')
  54. self.node7 = mptt.insert_node(target_id=self.node6, name='node7')
  55. self.node8 = mptt.insert_node(target_id=self.node6, name='node8')
  56. self.node9 = mptt.insert_node(target_id=None, name='node9')
  57. self.node10 = mptt.insert_node(target_id=self.node9, name='node10')
  58. self.node11 = mptt.insert_node(target_id=self.node9, name='node11')
  59. # name, parent, tree_id, level, left, right, structure
  60. # node1 - 1 0 1 16 node1
  61. # node2 node1 1 1 2 9 +-- node2
  62. # node3 node2 1 2 3 4 | |-- node3
  63. # node4 node2 1 2 5 6 | |-- node4
  64. # node5 node2 1 2 7 8 | +-- node5
  65. # node6 node1 1 1 10 15 +-- node6
  66. # node7 node6 1 2 11 12 |-- node7
  67. # node8 node6 1 2 13 14 +-- node8
  68. # node9 - 2 0 1 6 node9
  69. # node10 node9 2 1 2 3 |-- node10
  70. # node11 node9 2 1 4 5 +-- node11
  71. def build_tree2(self):
  72. self.node1 = mptt.insert_node(target_id=None, name='node1')
  73. self.node2 = mptt.insert_node(target_id=self.node1, name='node2')
  74. self.node3 = mptt.insert_node(target_id=self.node2, name='node3')
  75. self.node4 = mptt.insert_node(target_id=self.node2, name='node4')
  76. self.node5 = mptt.insert_node(target_id=self.node1, name='node5')
  77. self.node6 = mptt.insert_node(target_id=self.node5, name='node6')
  78. self.node7 = mptt.insert_node(target_id=self.node5, name='node7')
  79. self.node8 = mptt.insert_node(target_id=self.node1, name='node8')
  80. self.node9 = mptt.insert_node(target_id=self.node8, name='node9')
  81. self.node10 = mptt.insert_node(target_id=self.node8, name='node10')
  82. # name, parent, tree_id, level, left, right, structure
  83. # node1 - 1 0 1 20 node1
  84. # node2 node1 1 1 2 7 +-- node2
  85. # node3 node2 1 2 3 4 | |-- node3
  86. # node4 node2 1 2 5 6 | +-- node4
  87. # node5 node1 1 1 8 13 +-- node5
  88. # node6 node5 1 2 9 10 | |-- node6
  89. # node7 node5 1 2 11 12 | +-- node7
  90. # node8 node1 1 1 14 19 +-- node8
  91. # node9 node8 1 2 15 16 |-- node9
  92. # node10 node8 1 2 17 18 +-- node10
  93. class ReadingTestCase(unittest.TestCase, TreeTestMixin):
  94. def setUp(self):
  95. mptt.settings.table_node.truncate()
  96. self.build_tree1()
  97. def test_get_ancestors(self):
  98. def _get_ancestors(node_id, **kwds):
  99. return [node.name for node in mptt.get_ancestors(node_id, **kwds)]
  100. self.assertEqual(_get_ancestors(self.node1), [])
  101. self.assertEqual(_get_ancestors(self.node2), ['node1'])
  102. self.assertEqual(_get_ancestors(self.node3), ['node1', 'node2'])
  103. self.assertEqual(_get_ancestors(self.node4), ['node1', 'node2'])
  104. self.assertEqual(_get_ancestors(self.node5), ['node1', 'node2'])
  105. self.assertEqual(_get_ancestors(self.node6), ['node1'])
  106. self.assertEqual(_get_ancestors(self.node7), ['node1', 'node6'])
  107. self.assertEqual(_get_ancestors(self.node9), [])
  108. self.assertEqual(_get_ancestors(self.node10), ['node9'])
  109. self.assertEqual(_get_ancestors(self.node11), ['node9'])
  110. self.assertEqual(_get_ancestors(self.node1, include_self=True), ['node1'])
  111. self.assertEqual(_get_ancestors(self.node2, include_self=True), ['node1', 'node2'])
  112. self.assertEqual(_get_ancestors(self.node3, include_self=True), ['node1', 'node2', 'node3'])
  113. self.assertEqual(_get_ancestors(self.node7, include_self=True), ['node1', 'node6', 'node7'])
  114. self.assertEqual(_get_ancestors(self.node3, ascending=True), ['node2', 'node1'])
  115. self.assertEqual(_get_ancestors(self.node3, ascending=True, include_self=True), ['node3', 'node2', 'node1'])
  116. def test_descendants_from_node(self):
  117. def _get_descendants(node_id, **kwds):
  118. return [node.name for node
  119. in mptt.descendants_from_node(node_id).select(orderby=table_node.left)]
  120. self.assertEqual(_get_descendants(self.node1), ['node2', 'node3', 'node4', 'node5', 'node6', 'node7', 'node8'])
  121. self.assertEqual(_get_descendants(self.node2), ['node3', 'node4', 'node5'])
  122. self.assertEqual(_get_descendants(self.node3), [])
  123. self.assertEqual(_get_descendants(self.node6), ['node7', 'node8'])
  124. def test_get_descendant_count(self):
  125. self.assertEqual(mptt.get_descendant_count(self.node1), 7)
  126. self.assertEqual(mptt.get_descendant_count(self.node2), 3)
  127. self.assertEqual(mptt.get_descendant_count(self.node3), 0)
  128. self.assertEqual(mptt.get_descendant_count(self.node4), 0)
  129. self.assertEqual(mptt.get_descendant_count(self.node5), 0)
  130. self.assertEqual(mptt.get_descendant_count(self.node6), 2)
  131. self.assertEqual(mptt.get_descendant_count(self.node7), 0)
  132. self.assertEqual(mptt.get_descendant_count(self.node8), 0)
  133. self.assertEqual(mptt.get_descendant_count(self.node9), 2)
  134. self.assertEqual(mptt.get_descendant_count(self.node10), 0)
  135. def test_leafnodes(self):
  136. self.assertEqual([node.name for node in mptt.leafnodes().select(orderby=table_node.tree_id|table_node.left)],
  137. ['node3', 'node4', 'node5', 'node7', 'node8', 'node10', 'node11'])
  138. def test_get_roots(self):
  139. self.assertEqual([node.name for node in mptt.get_roots()],
  140. ['node1', 'node9'])
  141. def test_is_root_node(self):
  142. # id
  143. self.assertTrue(mptt.is_root_node(self.node1))
  144. self.assertFalse(mptt.is_root_node(self.node2))
  145. # Row
  146. self.assertTrue(mptt.is_root_node(table_node[self.node1]))
  147. self.assertFalse(mptt.is_root_node(table_node[self.node2]))
  148. # def test_is_child_node(self):
  149. # TODO
  150. # self.assertTrue(mptt.is_child_node(self.node1))
  151. # self.assertFalse(mptt.is_child_node(self.node2))
  152. class ReparentingTestCase(unittest.TestCase, TreeTestMixin):
  153. def setUp(self):
  154. mptt.settings.table_node.truncate()
  155. self.build_tree1()
  156. def test_new_root_from_subtree(self):
  157. mptt._make_child_root_node(self.node6)
  158. self.asserTree([self.get_node(self.node6)], 'node6 None 3 0 1 6')
  159. self.asserTree(self.get_all_nodes(),
  160. """node1 None 1 0 1 10
  161. node2 node1 1 1 2 9
  162. node3 node2 1 2 3 4
  163. node4 node2 1 2 5 6
  164. node5 node2 1 2 7 8
  165. node9 None 2 0 1 6
  166. node10 node9 2 1 2 3
  167. node11 node9 2 1 4 5
  168. node6 None 3 0 1 6
  169. node7 node6 3 1 2 3
  170. node8 node6 3 1 4 5""")
  171. def test_new_root_from_leaf_with_siblings(self):
  172. mptt._make_sibling_of_root_node(self.node3, self.node9, 'right')
  173. self.asserTree([self.get_node(self.node3)], 'node3 None 3 0 1 2')
  174. self.asserTree(self.get_all_nodes(),
  175. """node1 None 1 0 1 14
  176. node2 node1 1 1 2 7
  177. node4 node2 1 2 3 4
  178. node5 node2 1 2 5 6
  179. node6 node1 1 1 8 13
  180. node7 node6 1 2 9 10
  181. node8 node6 1 2 11 12
  182. node9 None 2 0 1 6
  183. node10 node9 2 1 2 3
  184. node11 node9 2 1 4 5
  185. node3 None 3 0 1 2""")
  186. def test_new_child_from_root(self):
  187. mptt.move_node(self.node1, self.node9)
  188. self.asserTree([self.get_node(self.node1)], 'node1 node9 2 1 6 21')
  189. self.asserTree(self.get_all_nodes(),
  190. """node1 node9 2 1 6 21
  191. node2 node1 2 2 7 14
  192. node3 node2 2 3 8 9
  193. node4 node2 2 3 10 11
  194. node5 node2 2 3 12 13
  195. node6 node1 2 2 15 20
  196. node7 node6 2 3 16 17
  197. node8 node6 2 3 18 19
  198. node9 None 2 0 1 22
  199. node10 node9 2 1 2 3
  200. node11 node9 2 1 4 5""")
  201. def test_move_leaf_to_other_tree(self):
  202. mptt.move_node(self.node8, self.node9)
  203. self.asserTree([self.get_node(self.node8)], 'node8 node9 2 1 6 7')
  204. self.asserTree(self.get_all_nodes(),
  205. """node1 None 1 0 1 14
  206. node2 node1 1 1 2 9
  207. node3 node2 1 2 3 4
  208. node4 node2 1 2 5 6
  209. node5 node2 1 2 7 8
  210. node6 node1 1 1 10 13
  211. node7 node6 1 2 11 12
  212. node8 node9 2 1 6 7
  213. node9 None 2 0 1 8
  214. node10 node9 2 1 2 3
  215. node11 node9 2 1 4 5""")
  216. def test_move_subtree_to_other_tree(self):
  217. mptt.move_node(self.node6, self.node11)
  218. self.asserTree([self.get_node(self.node6)], 'node6 node11 2 2 5 10')
  219. self.asserTree(self.get_all_nodes(),
  220. """node1 None 1 0 1 10
  221. node2 node1 1 1 2 9
  222. node3 node2 1 2 3 4
  223. node4 node2 1 2 5 6
  224. node5 node2 1 2 7 8
  225. node6 node11 2 2 5 10
  226. node7 node6 2 3 6 7
  227. node8 node6 2 3 8 9
  228. node9 None 2 0 1 12
  229. node10 node9 2 1 2 3
  230. node11 node9 2 1 4 11""")
  231. def test_move_child_up_level(self):
  232. mptt.move_node(self.node8, self.node1)
  233. self.asserTree([self.get_node(self.node8)], 'node8 node1 1 1 14 15')
  234. self.asserTree(self.get_all_nodes(),
  235. """node1 None 1 0 1 16
  236. node2 node1 1 1 2 9
  237. node3 node2 1 2 3 4
  238. node4 node2 1 2 5 6
  239. node5 node2 1 2 7 8
  240. node6 node1 1 1 10 13
  241. node7 node6 1 2 11 12
  242. node8 node1 1 1 14 15
  243. node9 None 2 0 1 6
  244. node10 node9 2 1 2 3
  245. node11 node9 2 1 4 5""")
  246. def test_move_subtree_down_level(self):
  247. mptt.move_node(self.node6, self.node2)
  248. self.asserTree([self.get_node(self.node6)], 'node6 node2 1 2 9 14')
  249. self.asserTree(self.get_all_nodes(),
  250. """node1 None 1 0 1 16
  251. node2 node1 1 1 2 15
  252. node3 node2 1 2 3 4
  253. node4 node2 1 2 5 6
  254. node5 node2 1 2 7 8
  255. node6 node2 1 2 9 14
  256. node7 node6 1 3 10 11
  257. node8 node6 1 3 12 13
  258. node9 None 2 0 1 6
  259. node10 node9 2 1 2 3
  260. node11 node9 2 1 4 5""")
  261. def test_move_to(self):
  262. mptt.move_node(self.node9, self.node1)
  263. self.assertEqual(self.get_node(self.node9).parent, self.get_node(self.node1).id)
  264. class DeletionTestCase(unittest.TestCase, TreeTestMixin):
  265. def setUp(self):
  266. mptt.settings.table_node.truncate()
  267. self.build_tree2()
  268. def test_delete_root_node(self):
  269. mptt.insert_node(target_id=self.node1, position='left', name='node11')
  270. mptt.insert_node(target_id=self.node1, position='right', name='node12')
  271. self.asserTree(self.get_all_nodes(),
  272. """node11 None 1 0 1 2
  273. node1 None 2 0 1 20
  274. node2 node1 2 1 2 7
  275. node3 node2 2 2 3 4
  276. node4 node2 2 2 5 6
  277. node5 node1 2 1 8 13
  278. node6 node5 2 2 9 10
  279. node7 node5 2 2 11 12
  280. node8 node1 2 1 14 19
  281. node9 node8 2 2 15 16
  282. node10 node8 2 2 17 18
  283. node12 None 3 0 1 2""")
  284. mptt.delete_node(self.node1)
  285. self.asserTree(self.get_all_nodes(),
  286. """node11 None 1 0 1 2
  287. node12 None 3 0 1 2""")
  288. def test_delete_last_node_with_sibling(self):
  289. mptt.delete_node(self.node9)
  290. self.asserTree(self.get_all_nodes(),
  291. """node1 None 1 0 1 18
  292. node2 node1 1 1 2 7
  293. node3 node2 1 2 3 4
  294. node4 node2 1 2 5 6
  295. node5 node1 1 1 8 13
  296. node6 node5 1 2 9 10
  297. node7 node5 1 2 11 12
  298. node8 node1 1 1 14 17
  299. node10 node8 1 2 15 16""")
  300. def test_delete_last_node_with_descendants(self):
  301. mptt.delete_node(self.node8)
  302. self.asserTree(self.get_all_nodes(),
  303. """node1 None 1 0 1 14
  304. node2 node1 1 1 2 7
  305. node3 node2 1 2 3 4
  306. node4 node2 1 2 5 6
  307. node5 node1 1 1 8 13
  308. node6 node5 1 2 9 10
  309. node7 node5 1 2 11 12""")
  310. def test_delete_node_with_siblings(self):
  311. mptt.delete_node(self.node6)
  312. self.asserTree(self.get_all_nodes(),
  313. """node1 None 1 0 1 18
  314. node2 node1 1 1 2 7
  315. node3 node2 1 2 3 4
  316. node4 node2 1 2 5 6
  317. node5 node1 1 1 8 11
  318. node7 node5 1 2 9 10
  319. node8 node1 1 1 12 17
  320. node9 node8 1 2 13 14
  321. node10 node8 1 2 15 16""")
  322. def test_delete_node_with_descendants_and_siblings(self):
  323. mptt.delete_node(self.node5)
  324. self.asserTree(self.get_all_nodes(),
  325. """node1 None 1 0 1 14
  326. node2 node1 1 1 2 7
  327. node3 node2 1 2 3 4
  328. node4 node2 1 2 5 6
  329. node8 node1 1 1 8 13
  330. node9 node8 1 2 9 10
  331. node10 node8 1 2 11 12""")
  332. def run_test(TestCase):
  333. import cStringIO
  334. stream = cStringIO.StringIO()
  335. suite = unittest.TestLoader().loadTestsFromTestCase(TestCase)
  336. unittest.TextTestRunner(stream=stream, verbosity=2).run(suite)
  337. return stream.getvalue()
  338. def test():
  339. test_case_classes = []
  340. if request.args(0) in ('reading', None):
  341. test_case_classes.append(ReadingTestCase)
  342. if request.args(0) in ('reparenting', None):
  343. test_case_classes.append(ReparentingTestCase)
  344. if request.args(0) in ('deletion', None):
  345. test_case_classes.append(DeletionTestCase)
  346. return dict(back=A('back', _href=URL('index')),
  347. output=CODE(*[run_test(t) for t in test_case_classes]))