django-fast-treenode 2.1.4__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. django_fast_treenode-3.0.0.dist-info/METADATA +203 -0
  2. django_fast_treenode-3.0.0.dist-info/RECORD +90 -0
  3. {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.0.dist-info}/WHEEL +1 -1
  4. treenode/admin/__init__.py +2 -7
  5. treenode/admin/admin.py +138 -209
  6. treenode/admin/changelist.py +21 -39
  7. treenode/admin/exporter.py +170 -0
  8. treenode/admin/importer.py +171 -0
  9. treenode/admin/mixin.py +291 -0
  10. treenode/apps.py +42 -20
  11. treenode/cache.py +192 -303
  12. treenode/forms.py +45 -65
  13. treenode/managers/__init__.py +4 -20
  14. treenode/managers/managers.py +216 -0
  15. treenode/managers/queries.py +233 -0
  16. treenode/managers/tasks.py +167 -0
  17. treenode/models/__init__.py +8 -5
  18. treenode/models/decorators.py +54 -0
  19. treenode/models/factory.py +44 -68
  20. treenode/models/mixins/__init__.py +2 -1
  21. treenode/models/mixins/ancestors.py +44 -20
  22. treenode/models/mixins/children.py +33 -26
  23. treenode/models/mixins/descendants.py +33 -22
  24. treenode/models/mixins/family.py +25 -15
  25. treenode/models/mixins/logical.py +23 -21
  26. treenode/models/mixins/node.py +162 -104
  27. treenode/models/mixins/properties.py +22 -16
  28. treenode/models/mixins/roots.py +59 -15
  29. treenode/models/mixins/siblings.py +46 -43
  30. treenode/models/mixins/tree.py +212 -153
  31. treenode/models/mixins/update.py +154 -0
  32. treenode/models/models.py +365 -0
  33. treenode/settings.py +28 -0
  34. treenode/static/{treenode/css → css}/tree_widget.css +1 -1
  35. treenode/static/{treenode/css → css}/treenode_admin.css +43 -2
  36. treenode/static/css/treenode_tabs.css +51 -0
  37. treenode/static/js/lz-string.min.js +1 -0
  38. treenode/static/{treenode/js → js}/tree_widget.js +9 -23
  39. treenode/static/js/treenode_admin.js +531 -0
  40. treenode/static/vendors/jquery-ui/AUTHORS.txt +384 -0
  41. treenode/static/vendors/jquery-ui/LICENSE.txt +43 -0
  42. treenode/static/vendors/jquery-ui/external/jquery/jquery.js +10716 -0
  43. treenode/static/vendors/jquery-ui/images/ui-icons_444444_256x240.png +0 -0
  44. treenode/static/vendors/jquery-ui/images/ui-icons_555555_256x240.png +0 -0
  45. treenode/static/vendors/jquery-ui/images/ui-icons_777620_256x240.png +0 -0
  46. treenode/static/vendors/jquery-ui/images/ui-icons_777777_256x240.png +0 -0
  47. treenode/static/vendors/jquery-ui/images/ui-icons_cc0000_256x240.png +0 -0
  48. treenode/static/vendors/jquery-ui/images/ui-icons_ffffff_256x240.png +0 -0
  49. treenode/static/vendors/jquery-ui/index.html +297 -0
  50. treenode/static/vendors/jquery-ui/jquery-ui.css +438 -0
  51. treenode/static/vendors/jquery-ui/jquery-ui.js +5223 -0
  52. treenode/static/vendors/jquery-ui/jquery-ui.min.css +7 -0
  53. treenode/static/vendors/jquery-ui/jquery-ui.min.js +6 -0
  54. treenode/static/vendors/jquery-ui/jquery-ui.structure.css +16 -0
  55. treenode/static/vendors/jquery-ui/jquery-ui.structure.min.css +5 -0
  56. treenode/static/vendors/jquery-ui/jquery-ui.theme.css +439 -0
  57. treenode/static/vendors/jquery-ui/jquery-ui.theme.min.css +5 -0
  58. treenode/static/vendors/jquery-ui/package.json +82 -0
  59. treenode/templates/admin/treenode_changelist.html +25 -0
  60. treenode/templates/admin/treenode_import_export.html +85 -0
  61. treenode/templates/admin/treenode_rows.html +57 -0
  62. treenode/tests.py +3 -0
  63. treenode/urls.py +6 -27
  64. treenode/utils/__init__.py +0 -15
  65. treenode/utils/db/__init__.py +7 -0
  66. treenode/utils/db/compiler.py +114 -0
  67. treenode/utils/db/db_vendor.py +50 -0
  68. treenode/utils/db/service.py +84 -0
  69. treenode/utils/db/sqlcompat.py +60 -0
  70. treenode/utils/db/sqlquery.py +70 -0
  71. treenode/version.py +2 -2
  72. treenode/views/__init__.py +5 -0
  73. treenode/views/autoapi.py +91 -0
  74. treenode/views/autocomplete.py +52 -0
  75. treenode/views/children.py +41 -0
  76. treenode/views/common.py +23 -0
  77. treenode/views/crud.py +209 -0
  78. treenode/views/search.py +48 -0
  79. treenode/widgets.py +27 -44
  80. django_fast_treenode-2.1.4.dist-info/METADATA +0 -166
  81. django_fast_treenode-2.1.4.dist-info/RECORD +0 -63
  82. treenode/admin/mixins.py +0 -302
  83. treenode/managers/adjacency.py +0 -205
  84. treenode/managers/closure.py +0 -278
  85. treenode/models/adjacency.py +0 -342
  86. treenode/models/classproperty.py +0 -27
  87. treenode/models/closure.py +0 -122
  88. treenode/static/treenode/js/.gitkeep +0 -1
  89. treenode/static/treenode/js/treenode_admin.js +0 -131
  90. treenode/templates/admin/export_success.html +0 -26
  91. treenode/templates/admin/tree_node_changelist.html +0 -19
  92. treenode/templates/admin/tree_node_export.html +0 -27
  93. treenode/templates/admin/tree_node_import.html +0 -45
  94. treenode/templates/admin/tree_node_import_report.html +0 -32
  95. treenode/templates/widgets/tree_widget.css +0 -23
  96. treenode/utils/aid.py +0 -46
  97. treenode/utils/base16.py +0 -38
  98. treenode/utils/base36.py +0 -37
  99. treenode/utils/db.py +0 -116
  100. treenode/utils/exporter.py +0 -196
  101. treenode/utils/importer.py +0 -328
  102. treenode/utils/radix.py +0 -61
  103. treenode/views.py +0 -184
  104. {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.0.dist-info/licenses}/LICENSE +0 -0
  105. {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.0.dist-info}/top_level.txt +0 -0
  106. /treenode/static/{treenode → css}/.gitkeep +0 -0
  107. /treenode/static/{treenode/css → js}/.gitkeep +0 -0
@@ -2,13 +2,24 @@
2
2
  """
3
3
  TreeNode Descendants Mixin
4
4
 
5
- Version: 2.1.0
5
+ Version: 3.0.0
6
6
  Author: Timur Kady
7
7
  Email: timurkady@yandex.com
8
8
  """
9
9
 
10
10
  from django.db import models
11
- from treenode.cache import cached_method
11
+ from django.db.models import Q
12
+ from ...cache import cached_method
13
+
14
+
15
+ '''
16
+ try:
17
+ profile
18
+ except NameError:
19
+ def profile(func):
20
+ """Profile."""
21
+ return func
22
+ '''
12
23
 
13
24
 
14
25
  class TreeNodeFamilyMixin(models.Model):
@@ -19,7 +30,6 @@ class TreeNodeFamilyMixin(models.Model):
19
30
 
20
31
  abstract = True
21
32
 
22
- @cached_method
23
33
  def get_family_queryset(self):
24
34
  """
25
35
  Return node family.
@@ -27,13 +37,10 @@ class TreeNodeFamilyMixin(models.Model):
27
37
  Return a QuerySet containing the ancestors, itself and the descendants,
28
38
  in tree order.
29
39
  """
30
- model = self._meta.model
31
- queryset = model.objects.filter(
32
- models.Q(tn_closure__child=self.pk) |
33
- models.Q(tn_closure__parent=self.pk) |
34
- models.Q(pk=self.pk)
35
- ).distinct().order_by("tn_closure__depth", "tn_parent", "tn_priority")
36
- return queryset
40
+ return self._meta.model.objects.filter(
41
+ Q(pk__in=self._get_path()) |
42
+ Q(_path__startswith=self._path+'.')
43
+ )
37
44
 
38
45
  @cached_method
39
46
  def get_family_pks(self):
@@ -43,9 +50,10 @@ class TreeNodeFamilyMixin(models.Model):
43
50
  Return a pk-list containing the ancestors, the model itself and
44
51
  the descendants, in tree order.
45
52
  """
46
- pks = self.get_family_queryset().values_list("id", flat=True)
47
- return list(pks)
53
+ return self.query(objects="family")
48
54
 
55
+ # @profile
56
+ @cached_method
49
57
  def get_family(self):
50
58
  """
51
59
  Return node family.
@@ -53,11 +61,13 @@ class TreeNodeFamilyMixin(models.Model):
53
61
  Return a list containing the ancestors, the model itself and
54
62
  the descendants, in tree order.
55
63
  """
56
- queryset = self.get_family_queryset()
57
- return list(queryset)
64
+ ancestors = self.get_ancestors()
65
+ descendants = self.get_descendants()
66
+ return ancestors.extend(descendants)
58
67
 
68
+ @cached_method
59
69
  def get_family_count(self):
60
70
  """Return number of nodes in family."""
61
- return self.get_family_queryset().count()
71
+ return self.query(objects="family", mode='count')
62
72
 
63
73
  # The End
@@ -2,7 +2,7 @@
2
2
  """
3
3
  TreeNode Logical methods Mixin
4
4
 
5
- Version: 2.1.0
5
+ Version: 3.0.0
6
6
  Author: Timur Kady
7
7
  Email: timurkady@yandex.com
8
8
  """
@@ -18,51 +18,53 @@ class TreeNodeLogicalMixin(models.Model):
18
18
 
19
19
  abstract = True
20
20
 
21
- def is_ancestor_of(self, target_obj):
22
- """Return True if the current node is ancestor of target_obj."""
23
- return self in target_obj.get_ancestors(include_self=False)
21
+ def is_ancestor_of(self, target):
22
+ """Check if self is an ancestor of other node."""
23
+ return target.id in target.query("ancestors", include_self=False)
24
24
 
25
- def is_child_of(self, target_obj):
25
+ def is_child_of(self, target):
26
26
  """Return True if the current node is child of target_obj."""
27
- return self in target_obj.get_children()
27
+ return self._parent_id == target.id
28
28
 
29
- def is_descendant_of(self, target_obj):
30
- """Return True if the current node is descendant of target_obj."""
31
- return self in target_obj.get_descendants()
29
+ def is_descendant_of(self, target):
30
+ """Check if self is a descendant of other node."""
31
+ return target.id in target.query("descendants", include_self=False)
32
32
 
33
33
  def is_first_child(self):
34
34
  """Return True if the current node is the first child."""
35
- return self.tn_priority == 0
35
+ return self.priority == 0
36
36
 
37
37
  def has_children(self):
38
38
  """Return True if the node has children."""
39
- return self.tn_children.exists()
39
+ return self.query(objects="children", mode='exist')
40
40
 
41
41
  def is_last_child(self):
42
42
  """Return True if the current node is the last child."""
43
- return self.tn_priority == self.get_siblings_count() - 1
43
+ siblings_pks = self.query("siblings", include_self=True)
44
+ return siblings_pks[-1] == self.id
44
45
 
45
46
  def is_leaf(self):
46
47
  """Return True if the current node is a leaf."""
47
- return self.tn_children.count() == 0
48
+ return not self.has_children()
48
49
 
49
- def is_parent_of(self, target_obj):
50
+ def is_parent_of(self, target):
50
51
  """Return True if the current node is parent of target_obj."""
51
- return self == target_obj.tn_parent
52
+ return self.id == target._parent_id
52
53
 
53
54
  def is_root(self):
54
55
  """Return True if the current node is root."""
55
- return self.tn_parent is None
56
+ return self.parent is None
56
57
 
57
- def is_root_of(self, target_obj):
58
+ def is_root_of(self, target):
58
59
  """Return True if the current node is root of target_obj."""
59
- return self == target_obj.get_root()
60
+ return self.pk == target.query("ancestors")[0]
60
61
 
61
- def is_sibling_of(self, target_obj):
62
+ def is_sibling_of(self, target):
62
63
  """Return True if the current node is sibling of target_obj."""
63
- if target_obj.tn_parent is None and self.tn_parent is None:
64
+ if target.parent is None and self.parent is None:
64
65
  # Both objects are roots
65
66
  return True
66
- return (self.tn_parent == target_obj.tn_parent)
67
+ return (self._parent_id == target._parent_id)
68
+
67
69
 
68
70
  # The End
@@ -2,16 +2,21 @@
2
2
  """
3
3
  TreeNode Node Mixin
4
4
 
5
- Version: 2.1.3
5
+ Version: 3.0.0
6
6
  Author: Timur Kady
7
7
  Email: timurkady@yandex.com
8
8
  """
9
9
 
10
10
  from django.db import models
11
- from django.core.exceptions import FieldDoesNotExist
12
11
 
13
- from ...cache import cached_method, treenode_cache
14
- from ...utils.base36 import to_base36
12
+ from ...settings import BASE
13
+
14
+ try:
15
+ profile
16
+ except NameError:
17
+ def profile(func):
18
+ """Profile."""
19
+ return func
15
20
 
16
21
 
17
22
  class TreeNodeNodeMixin(models.Model):
@@ -22,72 +27,106 @@ class TreeNodeNodeMixin(models.Model):
22
27
 
23
28
  abstract = True
24
29
 
25
- @cached_method
26
30
  def get_breadcrumbs(self, attr='id'):
27
31
  """Optimized breadcrumbs retrieval with direct cache check."""
32
+ ancestors = self.get_ancestors()
33
+ return [getattr(n, attr, None) for n in ancestors]
28
34
 
29
- try:
30
- self._meta.get_field(attr)
31
- except FieldDoesNotExist:
32
- raise ValueError(f"Invalid attribute name: {attr}")
33
-
34
- ancestors = self.get_ancestors(include_self=True)
35
- return [getattr(node, attr) for node in ancestors]
36
-
37
- @cached_method
38
35
  def get_depth(self):
39
36
  """Get the node depth (self, how many levels of descendants)."""
40
- return self.closure_model.get_depth(self)
37
+ is_dry = getattr(self, "is_dry", True) or len(self.tasks.queue) > 0
38
+ if is_dry:
39
+ self.tasks.run()
40
+ self.refresh()
41
+ return self._depth
42
+
43
+ def distance_to(self, targer):
44
+ """Return number of edges on shortest path between two nodes."""
45
+ self_path = self.query(objects='ancestors')
46
+ targer_path = targer.query(objects='ancestors')
47
+
48
+ i = 0
49
+ for a, b in zip(self_path, targer_path):
50
+ if a != b:
51
+ break
52
+ i += 1
53
+
54
+ return (len(self_path) - i) + (len(targer_path) - i)
41
55
 
42
- @cached_method
43
56
  def get_index(self):
44
57
  """Get the node index (self, index in node.parent.children list)."""
45
- if self.tn_parent is None:
46
- return self.tn_priority
47
- source = list(self.tn_parent.tn_children.all())
48
- return source.index(self) if self in source else self.tn_priority
58
+ return self.priority
49
59
 
50
- @cached_method
51
60
  def get_level(self):
52
61
  """Get the node level (self, starting from 1)."""
53
- return self.closure_model.get_level(self)
62
+ is_dry = getattr(self, "is_dry", True)
63
+ if is_dry:
64
+ self.refresh()
65
+ return self._depth + 1
66
+
67
+ def get_left(self):
68
+ """Get the node to left."""
69
+ return self.get_previous_sibling()
70
+
71
+ def get_right(self):
72
+ """Get the node to right."""
73
+ return self.get_next_sibling()
54
74
 
55
75
  def get_order(self):
56
76
  """Return the materialized path."""
57
- path = self.get_breadcrumbs(attr='tn_priority')
58
- segments = [to_base36(i).rjust(6, '0') for i in path]
59
- return ''.join(segments)
77
+ is_dry = getattr(self, "is_dry", True) or len(self.tasks.queue) > 0
78
+ if is_dry:
79
+ self.tasks.run()
80
+ self.refresh()
81
+ return self._path
82
+
83
+ def get_path(self, prefix='', suffix='', delimiter='.', format_str=''):
84
+ """Return Materialized Path of node."""
85
+ priorities = self.get_breadcrumbs(attr='priority')
86
+ if not priorities or all(p is None for p in priorities):
87
+ return prefix + suffix
88
+
89
+ str_ = "{%s}" % format_str
90
+ path = delimiter.join([
91
+ str_.format(p)
92
+ for p in priorities
93
+ if p is not None
94
+ ])
95
+ return prefix + path + suffix
60
96
 
61
- def insert_at(self, target, position='first-child', save=False):
97
+ @classmethod
98
+ def shortest_path(cls, source, destination):
62
99
  """
63
- Insert a node into the tree relative to the target node.
100
+ Return the shortest path (as list of PKs).
64
101
 
65
- Parameters:
66
- target: еhe target node relative to which this node will be placed.
102
+ Returned value is pks from `source` to `destination`, going up to their
103
+ lowest common ancestor (LCA), then down to `destination`.
104
+ """
105
+ source_path = source.query(objects='ancestors')
106
+ destination_path = destination.query(objects='ancestors')
67
107
 
68
- position the position, relative to the target node, where the
69
- current node object will be moved to, can be one of:
108
+ # Find the divergence index
109
+ i = 0
110
+ for a, b in zip(source_path, destination_path):
111
+ if a != b:
112
+ break
113
+ i += 1
70
114
 
71
- - first-root: the node will be the first root node;
72
- - last-root: the node will be the last root node;
73
- - sorted-root: the new node will be moved after sorting by
74
- the treenode_sort_field field;
115
+ # Path up from source to LCA (excluding LCA)
116
+ up = source_path[:i-1:-1]
117
+ # path down from LCA to destination (including LCA)
118
+ down = destination_path[i-1:]
75
119
 
76
- - first-sibling: the node will be the new leftmost sibling of the
77
- target node;
78
- - left-sibling: the node will take the target node’s place, which will
79
- be moved to the target position with shifting follows nodes;
80
- - right-sibling: the node will be moved to the position after the
81
- target node;
82
- - last-sibling: the node will be the new rightmost sibling of the
83
- target node;
84
- - sorted-sibling: the new node will be moved after sorting by
85
- the treenode_sort_field field;
120
+ return up + down
86
121
 
87
- - first-child: the node will be the first child of the target node;
88
- - last-child: the node will be the new rightmost child of the target
89
- - sorted-child: the new node will be moved after sorting by
90
- the treenode_sort_field field.
122
+ def insert_at(self, target, position=None, save=False):
123
+ """
124
+ Insert a node into the tree relative to the target node.
125
+
126
+ Parameters:
127
+ target: еhe target node relative to which this node will be placed.
128
+ position – the position, relative to the target node, where the
129
+ current node object will be moved to, can be one of. Look _get_place().
91
130
 
92
131
  save : if `save=true`, the node will be saved in the tree. Otherwise,
93
132
  the method will return a model instance with updated fields: parent
@@ -100,18 +139,66 @@ class TreeNodeNodeMixin(models.Model):
100
139
  """
101
140
  # This method seems to have very dubious practical value.
102
141
  parent, priority = self._meta.model._get_place(target, position)
103
- self.tn_parent = parent
104
- self.tn_priority = priority
142
+ self.parent = parent
143
+ self.priority = priority
105
144
 
106
145
  if save:
107
146
  self.save()
108
147
 
109
- def move_to(self, target, position=0):
148
+ def move_to(self, target, position='last-child'):
110
149
  """
111
- Move node relative to target node and position.
150
+ Move node with subtree relative to target node and position.
112
151
 
113
- Moves the model instance relative to the target node and sets its
114
- position (if necessary).
152
+ Parameters:
153
+ target: еhe target node relative to which this node will be placed.
154
+ position – the position, relative to the target node, where the
155
+ current node object will be moved to, can be one of. Look _get_place().
156
+ """
157
+ parent, priority = self._meta.model._get_place(target, position)
158
+ self.parent = parent
159
+ self.priority = priority
160
+ self.save()
161
+
162
+ def get_parent(self):
163
+ """Get the parent node."""
164
+ return self.parent
165
+
166
+ def set_parent(self, parent_obj):
167
+ """Set the parent node."""
168
+ self.parent = parent_obj
169
+ self.save()
170
+
171
+ def get_parent_pk(self):
172
+ """Get the parent node pk."""
173
+ return self._parent_id
174
+
175
+ def get_priority(self):
176
+ """Get the node priority."""
177
+ return self.priority
178
+
179
+ def set_priority(self, priority=0):
180
+ """Set the node priority."""
181
+ self.priority = priority
182
+ self.save()
183
+
184
+ def get_root(self):
185
+ """Get the root node for the current node."""
186
+ if self.parent_id is None:
187
+ return self
188
+ root_pk = self.query(objects='root')[0]
189
+ return self._meta.model.objects.filter(pk=root_pk).first()
190
+
191
+ def get_root_pk(self):
192
+ """Get the root node pk for the current node."""
193
+ return self.query(objects='root')
194
+
195
+ # -----------------------------------------------------------------
196
+
197
+ @classmethod
198
+ # @profile
199
+ def _get_place(cls, target, position='last-child'):
200
+ """
201
+ Get position relative to the target node.
115
202
 
116
203
  position – the position, relative to the target node, where the
117
204
  current node object will be moved to, can be one of:
@@ -120,7 +207,6 @@ class TreeNodeNodeMixin(models.Model):
120
207
  - last-root: the node will be the last root node;
121
208
  - sorted-root: the new node will be moved after sorting by
122
209
  the treenode_sort_field field;
123
- Note: if `position` contains `root`, then `target` parametr is ignored
124
210
 
125
211
  - first-sibling: the node will be the new leftmost sibling of the
126
212
  target node;
@@ -136,59 +222,31 @@ class TreeNodeNodeMixin(models.Model):
136
222
  - first-child: the node will be the first child of the target node;
137
223
  - last-child: the node will be the new rightmost child of the target
138
224
  - sorted-child: the new node will be moved after sorting by
139
- the treenode_sort_field field;
140
- """
141
- parent, priority = self._meta.model._get_place(target, position)
142
- self.tn_parent = parent
143
- self.tn_priority = priority
144
- self.save()
145
-
146
- def get_path(self, prefix='', suffix='', delimiter='.', format_str=''):
147
- """Return Materialized Path of node."""
148
- priorities = self.get_breadcrumbs(attr='tn_priority')
149
- if not priorities or all(p is None for p in priorities):
150
- return prefix + suffix
151
-
152
- str_ = "{%s}" % format_str
153
- path = delimiter.join([
154
- str_.format(p)
155
- for p in priorities
156
- if p is not None
157
- ])
158
- return prefix + path + suffix
159
-
160
- @cached_method
161
- def get_parent(self):
162
- """Get the parent node."""
163
- return self.tn_parent
225
+ the treenode_sort_field field.
164
226
 
165
- def set_parent(self, parent_obj):
166
- """Set the parent node."""
167
- self.tn_parent = parent_obj
168
- self.save()
227
+ """
228
+ choices = {
229
+ "first-root": lambda: (None, 0),
230
+ "last-root": lambda: (None, BASE - 1),
231
+ "sorted-root": lambda: (None, 0),
169
232
 
170
- def get_parent_pk(self):
171
- """Get the parent node pk."""
172
- return self.get_parent().pk if self.tn_parent else None
233
+ "first-sibling": lambda: (target.parent, 0),
234
+ "left-sibling": lambda: (target.parent, target.priority),
235
+ "right-sibling": lambda: (target.parent, target.priority + 1),
236
+ "last-sibling": lambda: (target.parent, BASE - 1),
237
+ "sorted-sibling": lambda: (target.parent, 0),
173
238
 
174
- @cached_method
175
- def get_priority(self):
176
- """Get the node priority."""
177
- return self.tn_priority
239
+ "first-child": lambda: (target, 0),
240
+ "last-child": lambda: (target, BASE - 1),
241
+ "sorted-child": lambda: (target, 0),
242
+ }
178
243
 
179
- def set_priority(self, priority=0):
180
- """Set the node priority."""
181
- self.tn_priority = priority
182
- self.save()
244
+ if isinstance(position, int):
245
+ return target, position
246
+ elif not isinstance(position, str) or position not in choices:
247
+ raise ValueError(f"Invalid position format: {position}")
183
248
 
184
- @cached_method
185
- def get_root(self):
186
- """Get the root node for the current node."""
187
- return self.closure_model.get_root(self)
249
+ return choices[position]()
188
250
 
189
- def get_root_pk(self):
190
- """Get the root node pk for the current node."""
191
- root = self.get_root()
192
- return root.pk if root else None
193
251
 
194
252
  # The End
@@ -2,12 +2,12 @@
2
2
  """
3
3
  TreeNode Properties Mixin
4
4
 
5
- Version: 2.1.0
5
+ Version: 3.0.0
6
6
  Author: Timur Kady
7
7
  Email: timurkady@yandex.com
8
8
  """
9
9
 
10
- from ..classproperty import classproperty
10
+ from ..decorators import classproperty
11
11
 
12
12
 
13
13
  class TreeNodePropertiesMixin:
@@ -88,25 +88,35 @@ class TreeNodePropertiesMixin:
88
88
  """Get the last child node."""
89
89
  return self.get_last_child()
90
90
 
91
+ @property
92
+ def left(self):
93
+ """Get the node to left."""
94
+ return self.get_left()
95
+
96
+ @property
97
+ def right(self):
98
+ """Get the node to right."""
99
+ return self.get_right()
100
+
91
101
  @property
92
102
  def level(self):
93
103
  """Get the node level."""
94
104
  return self.get_level()
95
105
 
96
- @property
97
- def parent(self):
98
- """Get node parent."""
99
- return self.tn_parent
106
+ # @property
107
+ # def parent(self):
108
+ # """Get node parent."""
109
+ # return self.tn_parent
100
110
 
101
111
  @property
102
112
  def parent_pk(self):
103
113
  """Get node parent pk."""
104
114
  return self.get_parent_pk()
105
115
 
106
- @property
107
- def priority(self):
108
- """Get node priority."""
109
- return self.get_priority()
116
+ # @property
117
+ # def priority(self):
118
+ # """Get node priority."""
119
+ # return self.get_priority()
110
120
 
111
121
  @classproperty
112
122
  def roots(cls):
@@ -143,14 +153,10 @@ class TreeNodePropertiesMixin:
143
153
  """Get an n-dimensional dict representing the model tree."""
144
154
  return cls.get_tree()
145
155
 
146
- @classproperty
147
- def tree_display(cls):
148
- """Get a multiline string representing the model tree."""
149
- return cls.get_tree_display()
150
-
151
156
  @property
152
- def tn_order(self):
157
+ def order(self):
153
158
  """Return the materialized path."""
154
159
  return self.get_order()
155
160
 
161
+
156
162
  # The End