django-fast-treenode 2.1.4__py3-none-any.whl → 3.0.1__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.1.dist-info/METADATA +203 -0
- django_fast_treenode-3.0.1.dist-info/RECORD +90 -0
- {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.1.dist-info}/WHEEL +1 -1
- treenode/admin/__init__.py +2 -7
- treenode/admin/admin.py +138 -209
- 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 +41 -19
- 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.4.dist-info/METADATA +0 -166
- django_fast_treenode-2.1.4.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.4.dist-info → django_fast_treenode-3.0.1.dist-info/licenses}/LICENSE +0 -0
- {django_fast_treenode-2.1.4.dist-info → django_fast_treenode-3.0.1.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/roots.py
CHANGED
@@ -2,13 +2,23 @@
|
|
2
2
|
"""
|
3
3
|
TreeNode Roots 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 ...cache import cached_method
|
12
|
+
|
13
|
+
|
14
|
+
'''
|
15
|
+
try:
|
16
|
+
profile
|
17
|
+
except NameError:
|
18
|
+
def profile(func):
|
19
|
+
"""Profile."""
|
20
|
+
return func
|
21
|
+
'''
|
12
22
|
|
13
23
|
|
14
24
|
class TreeNodeRootsMixin(models.Model):
|
@@ -51,46 +61,80 @@ class TreeNodeRootsMixin(models.Model):
|
|
51
61
|
instance = cls(**kwargs)
|
52
62
|
|
53
63
|
parent, priority = cls._get_place(None, position)
|
54
|
-
instance.
|
55
|
-
instance.
|
64
|
+
instance.parent = None
|
65
|
+
instance.priority = priority
|
56
66
|
instance.save()
|
57
67
|
return instance
|
58
68
|
|
59
69
|
@classmethod
|
60
|
-
@cached_method
|
61
70
|
def get_roots_queryset(cls):
|
62
71
|
"""Get root nodes queryset with preloaded children."""
|
63
|
-
|
64
|
-
return qs
|
72
|
+
return cls.objects.filter(parent_id__isnull=True)
|
65
73
|
|
66
74
|
@classmethod
|
67
|
-
@cached_method
|
68
75
|
def get_roots_pks(cls):
|
69
76
|
"""Get a list with all root nodes."""
|
70
|
-
|
71
|
-
return
|
77
|
+
queryset = cls.objects.filter(parent_id__isnull=True)
|
78
|
+
return queryset.values_list("id", flat=True)
|
72
79
|
|
73
80
|
@classmethod
|
81
|
+
@cached_method
|
74
82
|
def get_roots(cls):
|
75
83
|
"""Get a list with all root nodes."""
|
76
|
-
|
77
|
-
return list(qs)
|
84
|
+
return list(cls.objects.filter(parent__isnull=True))
|
78
85
|
|
79
86
|
@classmethod
|
80
87
|
def get_roots_count(cls):
|
81
88
|
"""Get a list with all root nodes."""
|
82
|
-
return
|
89
|
+
return cls.objects.filter(parent__isnull=True).count()
|
83
90
|
|
84
91
|
@classmethod
|
85
92
|
def get_first_root(cls):
|
86
93
|
"""Return the first root node in the tree or None if it is empty."""
|
87
94
|
roots = cls.get_roots_queryset()
|
88
|
-
return roots.
|
95
|
+
return roots.first()
|
89
96
|
|
90
97
|
@classmethod
|
91
98
|
def get_last_root(cls):
|
92
99
|
"""Return the last root node in the tree or None if it is empty."""
|
93
100
|
roots = cls.get_roots_queryset()
|
94
|
-
return roots.last()
|
101
|
+
return roots.last()
|
102
|
+
|
103
|
+
@classmethod
|
104
|
+
def sort_roots(cls):
|
105
|
+
"""
|
106
|
+
Re-sort root nodes.
|
107
|
+
|
108
|
+
Sorts all nodes with parent_id IS NULL using a raw SQL query with
|
109
|
+
a window function.
|
110
|
+
The new ordering is computed based on the model's sorting_field
|
111
|
+
(defaulting to 'priority').
|
112
|
+
It updates the 'priority' field for all root nodes.
|
113
|
+
"""
|
114
|
+
from django.db import connection
|
115
|
+
|
116
|
+
db_table = cls._meta.db_table
|
117
|
+
ordering_field = cls.sorting_field
|
118
|
+
|
119
|
+
# Only root nodes have parent_id IS NULL
|
120
|
+
where_clause = "parent_id IS NULL"
|
121
|
+
params = []
|
122
|
+
|
123
|
+
query = f"""
|
124
|
+
WITH ranked AS (
|
125
|
+
SELECT id,
|
126
|
+
ROW_NUMBER() OVER (ORDER BY {ordering_field}) - 1 AS new_priority
|
127
|
+
FROM {db_table}
|
128
|
+
WHERE {where_clause}
|
129
|
+
)
|
130
|
+
UPDATE {db_table} AS t
|
131
|
+
SET priority = ranked.new_priority
|
132
|
+
FROM ranked
|
133
|
+
WHERE t.id = ranked.id;
|
134
|
+
"""
|
135
|
+
|
136
|
+
with connection.cursor() as cursor:
|
137
|
+
cursor.execute(query, params)
|
138
|
+
|
95
139
|
|
96
140
|
# The End
|
@@ -2,13 +2,23 @@
|
|
2
2
|
"""
|
3
3
|
TreeNode Siblings 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 ...cache import cached_method
|
12
|
+
|
13
|
+
|
14
|
+
'''
|
15
|
+
try:
|
16
|
+
profile
|
17
|
+
except NameError:
|
18
|
+
def profile(func):
|
19
|
+
"""Profile."""
|
20
|
+
return func
|
21
|
+
'''
|
12
22
|
|
13
23
|
|
14
24
|
class TreeNodeSiblingsMixin(models.Model):
|
@@ -26,67 +36,59 @@ class TreeNodeSiblingsMixin(models.Model):
|
|
26
36
|
Returns the created node object or None if failed. It will be saved
|
27
37
|
by this method.
|
28
38
|
"""
|
29
|
-
if isinstance(position, int):
|
30
|
-
priority = position
|
31
|
-
parent = self.tn_parent
|
32
|
-
else:
|
33
|
-
if position not in [
|
34
|
-
'first-sibling', 'left-sibling', 'right-sibling',
|
35
|
-
'last-sibling', 'sorted-sibling']:
|
36
|
-
raise ValueError(f"Invalid position format: {position}")
|
37
|
-
parent, priority = self._meta.model._get_place(self, position)
|
38
|
-
|
39
39
|
instance = kwargs.get("instance")
|
40
40
|
if instance is None:
|
41
41
|
instance = self._meta.model(**kwargs)
|
42
|
-
|
43
|
-
|
42
|
+
|
43
|
+
parent, priority = self._meta.model._get_place(self.patent, position)
|
44
|
+
|
45
|
+
instance.parent = parent
|
46
|
+
instance.priority = priority
|
44
47
|
instance.save()
|
48
|
+
if isinstance(position, str) and 'sorted' in position:
|
49
|
+
self._sort_siblings()
|
45
50
|
return instance
|
46
51
|
|
47
|
-
@cached_method
|
48
52
|
def get_siblings_queryset(self, include_self=True):
|
49
|
-
"""Get
|
50
|
-
|
51
|
-
|
52
|
-
else:
|
53
|
-
qs = self._meta.model.objects.filter(tn_parent__isnull=True)
|
54
|
-
return qs if include_self else qs.exclude(pk=self.pk)
|
53
|
+
"""Get Siblings QuerySet."""
|
54
|
+
qs = self._meta.model.objects.filter(parent_id=self._parent_id)
|
55
|
+
return qs if include_self else qs.exclude(pk=self.id)
|
55
56
|
|
57
|
+
# @profile
|
58
|
+
@cached_method
|
56
59
|
def get_siblings(self, include_self=True):
|
57
60
|
"""Get a list with all the siblings."""
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
"""Get the siblings count."""
|
62
|
-
return self.get_siblings_queryset(include_self).count()
|
61
|
+
queryset = self._meta.model.objects.filter(parent=self.parent)
|
62
|
+
queryset = queryset if include_self else queryset.exclude(pk=self.pk)
|
63
|
+
return [n for n in queryset]
|
63
64
|
|
65
|
+
@cached_method
|
64
66
|
def get_siblings_pks(self, include_self=True):
|
65
67
|
"""Get the siblings pks list."""
|
66
|
-
return
|
68
|
+
return self.query("siblings", include_self)
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
|
70
|
+
@cached_method
|
71
|
+
def get_siblings_count(self):
|
72
|
+
"""Get the siblings count."""
|
73
|
+
qs = self.query("siblings")
|
74
|
+
return qs.count()
|
71
75
|
|
72
|
-
|
73
|
-
|
74
|
-
|
76
|
+
@cached_method
|
77
|
+
def get_first_sibling(self):
|
78
|
+
"""Return the first sibling in the tree, or None."""
|
79
|
+
qs = self._meta.model.objects.filter(parent_id=self._parent_id)
|
80
|
+
return qs.first()
|
75
81
|
|
82
|
+
@cached_method
|
76
83
|
def get_previous_sibling(self):
|
77
84
|
"""Return the previous sibling in the tree, or None."""
|
78
|
-
|
79
|
-
|
80
|
-
return None
|
81
|
-
return self.get_siblings_queryset.filter(tn_priority=priority)
|
85
|
+
options = {'parent_id': self._parent_id, 'priority__lt': self.priority}
|
86
|
+
return self._meta.model.objects.filter(**options).last()
|
82
87
|
|
83
88
|
def get_next_sibling(self):
|
84
89
|
"""Return the next sibling in the tree, or None."""
|
85
|
-
|
86
|
-
|
87
|
-
if priority == queryset.count():
|
88
|
-
return None
|
89
|
-
return queryset.filter(tn_priority=priority)
|
90
|
+
options = {'parent_id': self._parent_id, 'priority__gt': self.priority}
|
91
|
+
return self._meta.model.objects.filter(**options).first()
|
90
92
|
|
91
93
|
def get_last_sibling(self):
|
92
94
|
"""
|
@@ -94,6 +96,7 @@ class TreeNodeSiblingsMixin(models.Model):
|
|
94
96
|
|
95
97
|
Method can return the node itself if it was the leftmost sibling.
|
96
98
|
"""
|
97
|
-
|
99
|
+
qs = self._meta.model.objects.filter(parent_id=self._parent_id)
|
100
|
+
return qs.last()
|
98
101
|
|
99
102
|
# The End
|