django-fast-treenode 2.1.5__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.
- django_fast_treenode-3.0.0.dist-info/METADATA +203 -0
- django_fast_treenode-3.0.0.dist-info/RECORD +90 -0
- {django_fast_treenode-2.1.5.dist-info → django_fast_treenode-3.0.0.dist-info}/WHEEL +1 -1
- treenode/admin/__init__.py +0 -5
- treenode/admin/admin.py +137 -208
- treenode/admin/changelist.py +21 -39
- treenode/admin/exporter.py +170 -0
- treenode/admin/importer.py +171 -0
- treenode/admin/mixin.py +291 -0
- treenode/apps.py +42 -20
- treenode/cache.py +192 -303
- treenode/forms.py +45 -65
- treenode/managers/__init__.py +4 -20
- treenode/managers/managers.py +216 -0
- treenode/managers/queries.py +233 -0
- treenode/managers/tasks.py +167 -0
- treenode/models/__init__.py +8 -5
- treenode/models/decorators.py +54 -0
- treenode/models/factory.py +44 -68
- treenode/models/mixins/__init__.py +2 -1
- treenode/models/mixins/ancestors.py +44 -20
- treenode/models/mixins/children.py +33 -26
- treenode/models/mixins/descendants.py +33 -22
- treenode/models/mixins/family.py +25 -15
- treenode/models/mixins/logical.py +23 -21
- treenode/models/mixins/node.py +162 -104
- treenode/models/mixins/properties.py +22 -16
- treenode/models/mixins/roots.py +59 -15
- treenode/models/mixins/siblings.py +46 -43
- treenode/models/mixins/tree.py +212 -153
- treenode/models/mixins/update.py +154 -0
- treenode/models/models.py +365 -0
- treenode/settings.py +28 -0
- treenode/static/{treenode/css → css}/tree_widget.css +1 -1
- treenode/static/{treenode/css → css}/treenode_admin.css +43 -2
- treenode/static/css/treenode_tabs.css +51 -0
- treenode/static/js/lz-string.min.js +1 -0
- treenode/static/{treenode/js → js}/tree_widget.js +9 -23
- treenode/static/js/treenode_admin.js +531 -0
- treenode/static/vendors/jquery-ui/AUTHORS.txt +384 -0
- treenode/static/vendors/jquery-ui/LICENSE.txt +43 -0
- treenode/static/vendors/jquery-ui/external/jquery/jquery.js +10716 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_444444_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_555555_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_777620_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_777777_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_cc0000_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/images/ui-icons_ffffff_256x240.png +0 -0
- treenode/static/vendors/jquery-ui/index.html +297 -0
- treenode/static/vendors/jquery-ui/jquery-ui.css +438 -0
- treenode/static/vendors/jquery-ui/jquery-ui.js +5223 -0
- treenode/static/vendors/jquery-ui/jquery-ui.min.css +7 -0
- treenode/static/vendors/jquery-ui/jquery-ui.min.js +6 -0
- treenode/static/vendors/jquery-ui/jquery-ui.structure.css +16 -0
- treenode/static/vendors/jquery-ui/jquery-ui.structure.min.css +5 -0
- treenode/static/vendors/jquery-ui/jquery-ui.theme.css +439 -0
- treenode/static/vendors/jquery-ui/jquery-ui.theme.min.css +5 -0
- treenode/static/vendors/jquery-ui/package.json +82 -0
- treenode/templates/admin/treenode_changelist.html +25 -0
- treenode/templates/admin/treenode_import_export.html +85 -0
- treenode/templates/admin/treenode_rows.html +57 -0
- treenode/tests.py +3 -0
- treenode/urls.py +6 -27
- treenode/utils/__init__.py +0 -15
- treenode/utils/db/__init__.py +7 -0
- treenode/utils/db/compiler.py +114 -0
- treenode/utils/db/db_vendor.py +50 -0
- treenode/utils/db/service.py +84 -0
- treenode/utils/db/sqlcompat.py +60 -0
- treenode/utils/db/sqlquery.py +70 -0
- treenode/version.py +2 -2
- treenode/views/__init__.py +5 -0
- treenode/views/autoapi.py +91 -0
- treenode/views/autocomplete.py +52 -0
- treenode/views/children.py +41 -0
- treenode/views/common.py +23 -0
- treenode/views/crud.py +209 -0
- treenode/views/search.py +48 -0
- treenode/widgets.py +27 -44
- django_fast_treenode-2.1.5.dist-info/METADATA +0 -165
- django_fast_treenode-2.1.5.dist-info/RECORD +0 -63
- treenode/admin/mixins.py +0 -302
- treenode/managers/adjacency.py +0 -205
- treenode/managers/closure.py +0 -278
- treenode/models/adjacency.py +0 -342
- treenode/models/classproperty.py +0 -27
- treenode/models/closure.py +0 -122
- treenode/static/treenode/js/.gitkeep +0 -1
- treenode/static/treenode/js/treenode_admin.js +0 -131
- treenode/templates/admin/export_success.html +0 -26
- treenode/templates/admin/tree_node_changelist.html +0 -19
- treenode/templates/admin/tree_node_export.html +0 -27
- treenode/templates/admin/tree_node_import.html +0 -45
- treenode/templates/admin/tree_node_import_report.html +0 -32
- treenode/templates/widgets/tree_widget.css +0 -23
- treenode/utils/aid.py +0 -46
- treenode/utils/base16.py +0 -38
- treenode/utils/base36.py +0 -37
- treenode/utils/db.py +0 -116
- treenode/utils/exporter.py +0 -196
- treenode/utils/importer.py +0 -328
- treenode/utils/radix.py +0 -61
- treenode/views.py +0 -184
- {django_fast_treenode-2.1.5.dist-info → django_fast_treenode-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {django_fast_treenode-2.1.5.dist-info → django_fast_treenode-3.0.0.dist-info}/top_level.txt +0 -0
- /treenode/static/{treenode → css}/.gitkeep +0 -0
- /treenode/static/{treenode/css → js}/.gitkeep +0 -0
treenode/models/mixins/family.py
CHANGED
@@ -2,13 +2,24 @@
|
|
2
2
|
"""
|
3
3
|
TreeNode Descendants Mixin
|
4
4
|
|
5
|
-
Version:
|
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
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
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
|
-
|
57
|
-
|
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.
|
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:
|
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,
|
22
|
-
"""
|
23
|
-
return
|
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,
|
25
|
+
def is_child_of(self, target):
|
26
26
|
"""Return True if the current node is child of target_obj."""
|
27
|
-
return self
|
27
|
+
return self._parent_id == target.id
|
28
28
|
|
29
|
-
def is_descendant_of(self,
|
30
|
-
"""
|
31
|
-
return
|
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.
|
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.
|
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
|
-
|
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.
|
48
|
+
return not self.has_children()
|
48
49
|
|
49
|
-
def is_parent_of(self,
|
50
|
+
def is_parent_of(self, target):
|
50
51
|
"""Return True if the current node is parent of target_obj."""
|
51
|
-
return self ==
|
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.
|
56
|
+
return self.parent is None
|
56
57
|
|
57
|
-
def is_root_of(self,
|
58
|
+
def is_root_of(self, target):
|
58
59
|
"""Return True if the current node is root of target_obj."""
|
59
|
-
return self ==
|
60
|
+
return self.pk == target.query("ancestors")[0]
|
60
61
|
|
61
|
-
def is_sibling_of(self,
|
62
|
+
def is_sibling_of(self, target):
|
62
63
|
"""Return True if the current node is sibling of target_obj."""
|
63
|
-
if
|
64
|
+
if target.parent is None and self.parent is None:
|
64
65
|
# Both objects are roots
|
65
66
|
return True
|
66
|
-
return (self.
|
67
|
+
return (self._parent_id == target._parent_id)
|
68
|
+
|
67
69
|
|
68
70
|
# The End
|
treenode/models/mixins/node.py
CHANGED
@@ -2,16 +2,21 @@
|
|
2
2
|
"""
|
3
3
|
TreeNode Node Mixin
|
4
4
|
|
5
|
-
Version:
|
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 ...
|
14
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
97
|
+
@classmethod
|
98
|
+
def shortest_path(cls, source, destination):
|
62
99
|
"""
|
63
|
-
|
100
|
+
Return the shortest path (as list of PKs).
|
64
101
|
|
65
|
-
|
66
|
-
|
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
|
-
|
69
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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.
|
104
|
-
self.
|
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=
|
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
|
-
|
114
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
239
|
+
"first-child": lambda: (target, 0),
|
240
|
+
"last-child": lambda: (target, BASE - 1),
|
241
|
+
"sorted-child": lambda: (target, 0),
|
242
|
+
}
|
178
243
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
-
|
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:
|
5
|
+
Version: 3.0.0
|
6
6
|
Author: Timur Kady
|
7
7
|
Email: timurkady@yandex.com
|
8
8
|
"""
|
9
9
|
|
10
|
-
from ..
|
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
|
-
|
99
|
-
|
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
|
-
|
109
|
-
|
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
|
157
|
+
def order(self):
|
153
158
|
"""Return the materialized path."""
|
154
159
|
return self.get_order()
|
155
160
|
|
161
|
+
|
156
162
|
# The End
|