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
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+
4
+ Version: 3.0.0
5
+ Author: Timur Kady
6
+ Email: timurkady@yandex.com
7
+ """
8
+
9
+ from django.http import JsonResponse
10
+ from django.views import View
11
+ from django.contrib.admin.views.decorators import staff_member_required
12
+ from django.utils.decorators import method_decorator
13
+
14
+ from .common import get_model_from_request
15
+
16
+
17
+ @method_decorator(staff_member_required, name='dispatch')
18
+ class TreeChildrenView(View):
19
+ def get(self, request, *args, **kwargs):
20
+ model = get_model_from_request(request)
21
+ reference_id = request.GET.get("reference_id")
22
+ if not reference_id:
23
+ return JsonResponse({"results": []})
24
+
25
+ obj = model.objects.filter(pk=reference_id).first()
26
+ if not obj or obj.is_leaf():
27
+ return JsonResponse({"results": []})
28
+
29
+ results = [
30
+ {
31
+ "id": node.pk,
32
+ "text": str(node),
33
+ "level": node.get_depth(),
34
+ "is_leaf": node.is_leaf(),
35
+ }
36
+ for node in obj.get_children()
37
+ ]
38
+ return JsonResponse({"results": results})
39
+
40
+
41
+ # The End
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Thu Apr 10 19:50:23 2025
4
+
5
+ Version: 3.0.0
6
+ Author: Timur Kady
7
+ Email: timurkady@yandex.com
8
+ """
9
+
10
+ from django.apps import apps
11
+ from django.http import Http404
12
+
13
+
14
+ def get_model_from_request(request):
15
+ """Get model from request."""
16
+ model_label = request.GET.get("model")
17
+ if not model_label:
18
+ raise Http404("Missing 'model' parameter.")
19
+ try:
20
+ app_label, model_name = model_label.lower().split(".")
21
+ return apps.get_model(app_label, model_name)
22
+ except Exception:
23
+ raise Http404(f"Invalid model format: {model_label}")
treenode/views/crud.py ADDED
@@ -0,0 +1,209 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ API-First Support Module.
4
+
5
+ CRUD and Tree Operations for TreeNode models.
6
+
7
+ Version: 3.0.0
8
+ Author: Timur Kady
9
+ Email: timurkady@yandex.com
10
+ """
11
+
12
+ import json
13
+
14
+ from django.forms.models import model_to_dict
15
+ from django.http import (
16
+ JsonResponse, HttpResponseBadRequest, HttpResponseNotFound,
17
+ )
18
+
19
+ from django.views import View
20
+
21
+
22
+ class TreeNodeBaseAPIView(View):
23
+ """Simple API View for TreeNode-based models."""
24
+
25
+ model = None
26
+
27
+ def get_queryset(self):
28
+ """Return base queryset."""
29
+ return self.model.objects.get_queryset()
30
+
31
+ def dispatch(self, request, *args, **kwargs):
32
+ """Ddispatch request."""
33
+ self.action = kwargs.pop('action', None)
34
+ return super().dispatch(request, *args, **kwargs)
35
+
36
+ def get(self, request, pk=None):
37
+ """
38
+ Handle GET requests depending on action.
39
+
40
+ Get a list of all nodes:
41
+ GET /treenode/api/<model>/?flat=true
42
+
43
+ Get one node:
44
+ GET /treenode/api/<model>/<id>/
45
+
46
+ Get the whole tree:
47
+ GET /treenode/api/<model>/tree/
48
+
49
+ Get the annotated tree:
50
+ GET /treenode/api/<model>/tree/?annotated=true
51
+
52
+ Only node own children:
53
+ GET /treenode/api/<model>/<id>/children/
54
+
55
+ All descendants:
56
+ GET /treenode/api/<model>/<id>/descendants/
57
+
58
+ Get a family:
59
+ GET /treenode/api/<model>/<id>/family/
60
+ """
61
+ # Action mode
62
+ if self.action == "tree":
63
+ annotated = request.GET.get("annotated", "false").lower() == "true"
64
+ if annotated:
65
+ data = self.model.get_tree_annotated()
66
+ else:
67
+ data = self.model.get_tree()
68
+ return JsonResponse(data, safe=False)
69
+
70
+ if self.action == "children" and pk:
71
+ try:
72
+ node = self.get_queryset().get(pk=pk)
73
+ children = node.get_children()
74
+ data = [model_to_dict(obj) for obj in children]
75
+ return JsonResponse(data, safe=False)
76
+ except self.model.DoesNotExist:
77
+ return HttpResponseNotFound("Node not found.")
78
+
79
+ if self.action == "descendants" and pk:
80
+ try:
81
+ node = self.get_queryset().get(pk=pk)
82
+ descendants = node.get_descendants()
83
+ data = [model_to_dict(obj) for obj in descendants]
84
+ return JsonResponse(data, safe=False)
85
+ except self.model.DoesNotExist:
86
+ return HttpResponseNotFound("Node not found.")
87
+
88
+ if self.action == "family" and pk:
89
+ try:
90
+ node = self.get_queryset().get(pk=pk)
91
+ family = node.get_family()
92
+ data = [model_to_dict(obj) for obj in family]
93
+ return JsonResponse(data, safe=False)
94
+ except self.model.DoesNotExist:
95
+ return HttpResponseNotFound("Node not found.")
96
+
97
+ # Standard mode (if there is no special action)
98
+ if pk is None:
99
+ nodes = self.get_queryset().order_by("_path")
100
+ data = [model_to_dict(obj) for obj in nodes]
101
+ return JsonResponse(data, safe=False)
102
+ else:
103
+ try:
104
+ obj = self.get_queryset().get(pk=pk)
105
+ return JsonResponse(model_to_dict(obj))
106
+ except self.model.DoesNotExist:
107
+ return HttpResponseNotFound("Node not found.")
108
+
109
+ def post(self, request):
110
+ """
111
+ Create a new node.
112
+
113
+ POST /treenode/api/<model>/
114
+
115
+ Body (JSON):
116
+ {
117
+ "name": "Node Name",
118
+ "parent_id": 123, # optional
119
+ "priority": 0
120
+ }
121
+
122
+ Returns:
123
+ - Created node as JSON
124
+ """
125
+ try:
126
+ body = json.loads(request.body)
127
+ obj = self.model(**body)
128
+ obj.full_clean()
129
+ obj.save()
130
+ return JsonResponse(model_to_dict(obj), status=201)
131
+ except Exception as e:
132
+ return HttpResponseBadRequest(str(e))
133
+
134
+ def put(self, request, pk):
135
+ """
136
+ Replace a node.
137
+
138
+ PUT /treenode/api/<model>/<id>/
139
+
140
+ Body (JSON):
141
+ {
142
+ "name": "New Name",
143
+ "parent_id": 124,
144
+ "priority": 1
145
+ }
146
+
147
+ Returns:
148
+ - Updated node as JSON
149
+ """
150
+ try:
151
+ obj = self.get_queryset().get(pk=pk)
152
+ body = json.loads(request.body)
153
+ for field, value in body.items():
154
+ setattr(obj, field, value)
155
+ obj.full_clean()
156
+ obj.save()
157
+ return JsonResponse(model_to_dict(obj))
158
+ except self.model.DoesNotExist:
159
+ return HttpResponseNotFound("Node not found.")
160
+ except Exception as e:
161
+ return HttpResponseBadRequest(str(e))
162
+
163
+ def patch(self, request, pk):
164
+ """
165
+ Update a node (partially).
166
+
167
+ PATCH /treenode/api/<model>/<id>/
168
+
169
+ Body (JSON):
170
+ {
171
+ "priority": 2
172
+ }
173
+
174
+ Returns:
175
+ - Updated node as JSON
176
+ """
177
+ return self.put(request, pk)
178
+
179
+ def delete(self, request, pk):
180
+ """
181
+ Delete a node.
182
+
183
+ DELETE /treenode/api/<model>/<id>/
184
+ ?cascade=true # default, delete node and descendants
185
+ ?cascade=false # move children up before deleting
186
+
187
+ Returns:
188
+ {
189
+ "status": "deleted",
190
+ "id": <deleted id>,
191
+ "cascade": true|false
192
+ }
193
+ """
194
+ try:
195
+ cascade = not request.GET.get("cascade") == "false"
196
+ obj = self.get_queryset().get(pk=pk)
197
+ obj.delete(cascade=cascade)
198
+ return JsonResponse({
199
+ "status": "deleted",
200
+ "id": obj.pk,
201
+ "cascade": cascade
202
+ })
203
+ except self.model.DoesNotExist:
204
+ return HttpResponseNotFound("Node not found.")
205
+ except Exception as e:
206
+ return HttpResponseBadRequest(str(e))
207
+
208
+
209
+ # The End
@@ -0,0 +1,48 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Thu Apr 10 19:53:56 2025
4
+
5
+ Version: 3.0.0
6
+ Author: Timur Kady
7
+ Email: timurkady@yandex.com
8
+ """
9
+
10
+ from django.http import JsonResponse
11
+ from django.views import View
12
+ from django.utils.decorators import method_decorator
13
+ from django.contrib.admin.views.decorators import staff_member_required
14
+ from django.apps import apps
15
+ from django.db.models import Q
16
+
17
+
18
+ @method_decorator(staff_member_required, name="dispatch")
19
+ class TreeSearchView(View):
20
+ """Search view for TreeNode models used in admin interface."""
21
+
22
+ def get(self, request, *args, **kwargs):
23
+ app_label = request.GET.get("app")
24
+ model_name = request.GET.get("model")
25
+ query = request.GET.get("q", "").strip()
26
+
27
+ if not (app_label and model_name and query):
28
+ return JsonResponse({"results": []})
29
+
30
+ try:
31
+ model = apps.get_model(app_label, model_name)
32
+ except LookupError:
33
+ return JsonResponse({"results": []})
34
+
35
+ # Получаем queryset и ищем по __str__, через Q с contains
36
+ queryset = model.objects.all()
37
+ queryset = queryset.filter(
38
+ Q(name__icontains=query) | Q(pk__icontains=query))[:20]
39
+
40
+ results = [
41
+ {
42
+ "id": obj.pk,
43
+ "text": str(obj),
44
+ "is_leaf": obj.is_leaf(),
45
+ }
46
+ for obj in queryset
47
+ ]
48
+ return JsonResponse({"results": results})
treenode/widgets.py CHANGED
@@ -24,37 +24,22 @@ class TreeWidget(forms.Widget):
24
24
  class Media:
25
25
  """Meta class to define required CSS and JS files."""
26
26
 
27
- css = {"all": ("treenode/css/tree_widget.css",)}
28
- js = ("treenode/js/tree_widget.js",)
27
+ css = {"all": ("css/tree_widget.css",)}
28
+ js = ("js/tree_widget.js",)
29
29
 
30
30
  def build_attrs(self, base_attrs, extra_attrs=None):
31
31
  """Build attributes for the widget."""
32
32
  attrs = super().build_attrs(base_attrs, extra_attrs)
33
- attrs.setdefault("data-url", reverse("tree_autocomplete"))
34
- attrs.setdefault("data-url-children", reverse("tree_children"))
33
+ attrs.setdefault("data-url", reverse("treenode:tree_autocomplete"))
34
+ attrs.setdefault("data-url-children", reverse("treenode:tree_children"))
35
35
  attrs["class"] = f"{attrs.get('class', '')} tree-widget".strip()
36
36
 
37
- if "data-forward" not in attrs:
38
- model = getattr(self, "model", None)
39
-
40
- if not model and hasattr(self.choices, "queryset"):
41
- model = self.choices.queryset.model
42
- if model is None:
43
- raise ValueError("TreeWidget: model not passed or not defined")
44
-
45
- try:
46
- forward_data = json.dumps({"model": model._meta.label})
47
- attrs["data-forward"] = forward_data.replace('"', "&quot;")
48
- except AttributeError as e:
49
- raise ValueError("TreeWidget: invalid Django model") from e
50
-
51
- if self.choices:
52
- try:
53
- current_value = self.value()
54
- if current_value:
55
- attrs["data-selected"] = str(current_value)
56
- except Exception:
57
- pass
37
+ model = getattr(self, "model", None)
38
+ if not model and hasattr(self.choices, "queryset"):
39
+ model = self.choices.queryset.model
40
+ elif model is None:
41
+ raise ValueError("TreeWidget: model not passed or not defined")
42
+ attrs["data-model"] = model._meta.label
58
43
 
59
44
  return attrs
60
45
 
@@ -66,32 +51,30 @@ class TreeWidget(forms.Widget):
66
51
  """
67
52
  return []
68
53
 
54
+ def id_for_label(self, id_):
55
+ """Return label for field."""
56
+ return f"{id_}_search"
57
+
58
+ def label_from_instance(self, obj):
59
+ """Return instance label."""
60
+ return str(obj)
61
+
69
62
  def render(self, name, value, attrs=None, renderer=None):
70
63
  """Render widget as a hidden input + tree container structure."""
71
-
72
64
  attrs = self.build_attrs(attrs)
73
65
  attrs["name"] = name
74
66
  attrs["type"] = "hidden"
75
- if value:
76
- attrs["value"] = str(value)
67
+ attrs["value"] = str(value) if value else ""
77
68
 
78
69
  # If value is set, try to get string representation of instance,
79
70
  # otherwise print "Root"
80
- if value not in [None, "", "None"]:
81
- try:
82
- from django.apps import apps
83
- # Define the model: first self.model if it is defined,
84
- # otherwise via choices
85
- model = getattr(self, "model", None)
86
- if model is None and hasattr(self, "choices") and getattr(self.choices, "queryset", None):
87
- model = self.choices.queryset.model
88
- if model is not None:
89
- instance = model.objects.get(pk=value)
90
- selected_value = str(instance)
91
- else:
92
- selected_value = str(value)
93
- except Exception:
94
- selected_value = str(value)
71
+ if str(value).lower() not in ("", "none"):
72
+ model = self.model or self.choices.queryset.model
73
+ instance = model.objects.filter(pk=value).first()
74
+ if instance:
75
+ selected_value = str(instance)
76
+ else:
77
+ selected_value = _("Root")
95
78
  else:
96
79
  selected_value = _("Root")
97
80
 
@@ -111,7 +94,7 @@ class TreeWidget(forms.Widget):
111
94
  <div class="tree-widget-dropdown">
112
95
  <div class="tree-search-wrapper">
113
96
  <span class="tree-search-icon">&#x1F50E;&#xFE0E;</span>
114
- <input type="text" class="tree-search" placeholder="{search_placeholder}" />
97
+ <input id="id_parent_search" type="text" class="tree-search" placeholder="{search_placeholder}" />
115
98
  <button type="button" class="tree-search-clear">&times;</button>
116
99
  </div>
117
100
  <ul class="tree-list"></ul>
@@ -1,166 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: django-fast-treenode
3
- Version: 2.1.4
4
- Summary: Application for supporting tree (hierarchical) data structure in Django projects
5
- Home-page: https://django-fast-treenode.readthedocs.io/
6
- Author: Timur Kady
7
- Author-email: Timur Kady <timurkady@yandex.com>
8
- License: MIT License
9
-
10
- Copyright (c) 2020-2025 Timur Kady
11
-
12
- Permission is hereby granted, free of charge, to any person obtaining a copy
13
- of this software and associated documentation files (the "Software"), to deal
14
- in the Software without restriction, including without limitation the rights
15
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
- copies of the Software, and to permit persons to whom the Software is
17
- furnished to do so, subject to the following conditions:
18
-
19
- The above copyright notice and this permission notice shall be included in all
20
- copies or substantial portions of the Software.
21
-
22
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
- SOFTWARE.
29
-
30
- Project-URL: Homepage, https://github.com/TimurKady/django-fast-treenode
31
- Project-URL: Documentation, https://django-fast-treenode.readthedocs.io/
32
- Project-URL: Source, https://github.com/TimurKady/django-fast-treenode
33
- Project-URL: Issues, https://github.com/TimurKady/django-fast-treenode/issues
34
- Classifier: Development Status :: 5 - Production/Stable
35
- Classifier: Intended Audience :: Developers
36
- Classifier: Programming Language :: Python :: 3
37
- Classifier: Programming Language :: Python :: 3.9
38
- Classifier: Programming Language :: Python :: 3.10
39
- Classifier: Programming Language :: Python :: 3.11
40
- Classifier: Programming Language :: Python :: 3.12
41
- Classifier: Programming Language :: Python :: 3.13
42
- Classifier: Programming Language :: Python :: 3.14
43
- Classifier: Framework :: Django
44
- Classifier: Framework :: Django :: 4.0
45
- Classifier: Framework :: Django :: 4.1
46
- Classifier: Framework :: Django :: 4.2
47
- Classifier: Framework :: Django :: 5.0
48
- Classifier: Framework :: Django :: 5.1
49
- Classifier: Framework :: Django :: 5.2
50
- Classifier: License :: OSI Approved :: MIT License
51
- Classifier: Operating System :: OS Independent
52
- Requires-Python: >=3.9
53
- Description-Content-Type: text/markdown
54
- License-File: LICENSE
55
- Requires-Dist: Django>=4.0
56
- Requires-Dist: pympler>=1.0
57
- Requires-Dist: django-widget-tweaks>=1.5
58
- Requires-Dist: msgpack>=1.1
59
- Provides-Extra: import-export
60
- Requires-Dist: openpyxl; extra == "import-export"
61
- Requires-Dist: pyyaml; extra == "import-export"
62
- Requires-Dist: xlsxwriter; extra == "import-export"
63
-
64
- # Django-fast-treenode
65
- **Hybrid Tree Storage**
66
-
67
- [![Tests](https://github.com/TimurKady/django-fast-treenode/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/TimurKady/django-fast-treenode/actions/workflows/test.yaml)
68
- [![Docs](https://readthedocs.org/projects/django-fast-treenode/badge/?version=latest)](https://django-fast-treenode.readthedocs.io/)
69
- [![PyPI](https://img.shields.io/pypi/v/django-fast-treenode.svg)](https://pypi.org/project/django-fast-treenode/)
70
- [![Published on Django Packages](https://img.shields.io/badge/Published%20on-Django%20Packages-0c3c26)](https://djangopackages.org/packages/p/django-fast-treenode/)
71
- [![Sponsor](https://img.shields.io/github/sponsors/TimurKady)](https://github.com/sponsors/TimurKady)
72
-
73
- **Django Fast TreeNode** is a high-performance Django application for working with tree structures.
74
-
75
- ## Features
76
- - **Hybrid storage model**: Combines Adjacency List and Materialized Path (versions 2.2 and above) Closure Table (versions 2.1 and earlier) for optimal performance.
77
- - **Custom caching system**: A built-in caching mechanism, specifically designed for this package, significantly boosts execution speed.
78
- - **Efficient queries**: Retrieve ancestors, descendants, breadcrumbs, and tree depth with only one SQL queriy.
79
- - **Bulk operations**: Supports fast insertion, movement, and deletion of nodes.
80
- - **Flexibility**: Fully integrates with Django ORM and adapts to various business logic needs.
81
- - **Admin panel integration**: Full compatibility with Django's admin panel, allowing intuitive management of tree structures.
82
- - **Import & Export functionality**: Built-in support for importing and exporting tree structures in multiple formats (CSV, JSON, XLSX, YAML, TSV), including integration with the Django admin panel.
83
-
84
- It seems that django-fast-treenode is currently the most balanced and performant solution for most tasks, especially those related to dynamic hierarchical data structures. Check out the results of (comparison tests)[#] with other Django packages.
85
-
86
- ## Use Cases
87
- Django Fast TreeNode is suitable for a wide range of applications, from simple directories to complex systems with deep hierarchical structures:
88
- - **Categories and taxonomies**: Manage product categories, tags, and classification systems.
89
- - **Menus and navigation**: Create tree-like menus and nested navigation structures.
90
- - **Forums and comments**: Store threaded discussions and nested comment chains.
91
- - **Geographical data**: Represent administrative divisions, regions, and areas of influence.
92
- - **Organizational and Business Structures**: Model company hierarchies, business processes, employees and departments.
93
-
94
- In all applications, `django-fast-treenode` models show excellent performance and stability.
95
-
96
- ## Quick start
97
- 1. Run `pip install django-fast-treenode`.
98
- 2. Add `treenode` to `settings.INSTALLED_APPS`.
99
-
100
- ```python
101
- INSTALLED_APPS = [
102
- ...
103
- 'treenode',
104
- ]
105
- ```
106
-
107
- 3. Define your model inherit from `treenode.models.TreeNodeModel`.
108
-
109
- ```python
110
- from treenode.models import TreeNodeModel
111
-
112
- class Category(TreeNodeModel):
113
- name = models.CharField(max_length=255)
114
- treenode_display_field = "name"
115
- ```
116
-
117
- 4. Make your model-admin inherit from `treenode.admin.TreeNodeModelAdmin`.
118
-
119
- ```python
120
- from treenode.admin import TreeNodeModelAdmin
121
- from .models import Category
122
-
123
- @admin.register(Category)
124
- class CategoryAdmin(TreeNodeModelAdmin):
125
- list_display = ("name",)
126
- search_fields = ("name",)
127
- ```
128
- 5. Run migrations.
129
-
130
- ```bash
131
- python manage.py makemigrations
132
- python manage.py migrate
133
- ```
134
-
135
- 6. Run server and use!
136
-
137
- ```bash
138
- >>> root = Category.objects.create(name="Root")
139
- >>> child = Category.objects.create(name="Child")
140
- >>> child.set_parent(root)
141
- >>> root_descendants_list = root.get_descendants()
142
- >>> root_children_queryset = root.get_children_queryset()
143
- >>> ancestors_pks = child.get_ancestors_pks()
144
- ```
145
-
146
- ## Documentation
147
- Full documentation is available at **[ReadTheDocs](https://django-fast-treenode.readthedocs.io/)**.
148
-
149
- Quick access links:
150
- * [Installation, configuration and fine tuning](https://django-fast-treenode.readthedocs.io/installation/)
151
- * [Model Inheritance and Extensions](https://django-fast-treenode.readthedocs.io/models/)
152
- * [Working with Admin Classes](https://django-fast-treenode.readthedocs.io/admin/)
153
- * [API Reference](https://django-fast-treenode.readthedocs.io/api/)
154
- * [Import & Export](https://django-fast-treenode.readthedocs.io/import_export/)
155
- * [Caching and working with cache](https://django-fast-treenode.readthedocs.io/cache/)
156
- * [Migration and upgrade guide](https://django-fast-treenode.readthedocs.io/migration/)
157
-
158
- Your wishes, objections, comments are welcome.
159
-
160
- ## License
161
- Released under [MIT License](https://github.com/TimurKady/django-fast-treenode/blob/main/LICENSE).
162
-
163
- ## Credits
164
- Thanks to everyone who contributed to the development and testing of this package, as well as the Django community for their inspiration and support.
165
-
166
- Special thanks to [Fabio Caccamo](https://github.com/fabiocaccamo) for the idea behind creating a fast Django application for handling hierarchies.
@@ -1,63 +0,0 @@
1
- treenode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- treenode/apps.py,sha256=a7UasXiZZudPccjmHEudP79TkhR_53Mvnb-dBXLHRRQ,862
3
- treenode/cache.py,sha256=GoN2J-ypEQWIK05WSw9LYo7boKHGPXNFxqHorFPUqX8,12481
4
- treenode/forms.py,sha256=Mjrpuyd1CPsitcElDVagE3k-p2kU4xIlRuy1f5Zgt3c,3800
5
- treenode/signals.py,sha256=ERrlKjGqhYaPYVKKRk1JBBlPFOmJKpJ6bXsJavcTlo0,518
6
- treenode/urls.py,sha256=CsgX0hRyDVrMS8YnRlr_CxmDlgGIhDpqZ9ldoMYZCac,866
7
- treenode/version.py,sha256=QArF1TMhRPActA4pib2dl6jP5b51jCmD6-Wh2ROUD78,222
8
- treenode/views.py,sha256=rEZEgdbEA3AJDHrvtrAm-t60QTJcJ4JEhNsNMR1Y_I4,5549
9
- treenode/widgets.py,sha256=Mi0F-AK_UcmU6C50ENK9vv6xGQNuDtrtzXSnXSOXhLM,4760
10
- treenode/admin/__init__.py,sha256=TdlPIyRW8i9qTVqGLmLWiBw4DyoGHUYZErE6rCyGOPE,119
11
- treenode/admin/admin.py,sha256=6H3N2Dg6l-MrFwIcyKR5YENg0cEo-I4uKCP9MuhHkqo,10580
12
- treenode/admin/changelist.py,sha256=YZm3zNniX75CgLjnbHpVr0OIP91halDEBHmrcS8m5Og,2128
13
- treenode/admin/mixins.py,sha256=-dVZwEjKsfRzMkBe87dkI0SZ9MH45YE_o39SIhSJWy4,11194
14
- treenode/managers/__init__.py,sha256=EG_tj9P1Hama3kaqMfHck4lfzUWoPaJJVOXe3qaKMUo,585
15
- treenode/managers/adjacency.py,sha256=OOjHCSTo0aAcSxOOwz7OsQTGdTRkM1mAxSN7jlzRpho,7896
16
- treenode/managers/closure.py,sha256=PcScdJJUnLcKe8Y1wqROYPsRtAnBMUO4xn5sILk9AIM,10638
17
- treenode/models/__init__.py,sha256=pBiMlEpC_Thh7asraNzA7W_7BKu2oAHtcn-K6_sdJe8,112
18
- treenode/models/adjacency.py,sha256=QWGOidd4tH3afqVedPNQqeh-W-zUTNs1m-iAhCAXub4,12396
19
- treenode/models/classproperty.py,sha256=J4W6snsfsEUSHKHkIlM9yOJYQ_FSrp3P3oEYMKJengg,571
20
- treenode/models/closure.py,sha256=eZtLbnCOR1xYWhgbo1Pml_K0Pd0MM2DjiZl3SWMVe2A,3712
21
- treenode/models/factory.py,sha256=10FEGGC5PGWaR58qErs0oOrCS0KeI8x9H-SknZAAWqw,2291
22
- treenode/models/mixins/__init__.py,sha256=gTdMZFh1slNHMvxrnu-hGl46xqnWd4W7TOEFWTVJq40,757
23
- treenode/models/mixins/ancestors.py,sha256=QZywMcIVZK82j13QsgevVN2ZhRLa86DfRIt2BsiM2to,1526
24
- treenode/models/mixins/children.py,sha256=xgenQFyZBG7_S33QQlznSmNhXEdeo9DeLyi7dKmvFhw,2637
25
- treenode/models/mixins/descendants.py,sha256=PYYfd7oqlv3Gnfahm0u9ACHjpWSDNM6Z8oJaJXPVQ8w,1910
26
- treenode/models/mixins/family.py,sha256=h2IRRADkQxve97QqBHKv0evVz4cFQtcNR8CbPi9Ri_w,1645
27
- treenode/models/mixins/logical.py,sha256=jlhBSq3AfCYNyNjqyKM9siyioS3SYcGD-aG2b4MV2RM,2169
28
- treenode/models/mixins/node.py,sha256=VpLiFI1olvj5Gp2yV4n-aG4z4mZ7vOS6ytloWwO5s6w,7149
29
- treenode/models/mixins/properties.py,sha256=pfv80KLXcPeGx00IFCBcst1_cf0AmzhjshFjq1XQWMY,3876
30
- treenode/models/mixins/roots.py,sha256=MoFQq1fph70awc26UMUbfeTpt0ToUOvMz1c7LlDyIP8,2956
31
- treenode/models/mixins/siblings.py,sha256=JTQjaxnDH9t-AVMCQFiuc0nHLdIsE4v5vJ5z6LcUZLY,3236
32
- treenode/models/mixins/tree.py,sha256=CsO0ynwcwkrWgQbTzvF4yws-y7n1GGM2zImJH0hgV00,13042
33
- treenode/static/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- treenode/static/treenode/.gitkeep,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
35
- treenode/static/treenode/css/.gitkeep,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
36
- treenode/static/treenode/css/tree_widget.css,sha256=SE74hZaOECHe1VKe-N6b-MxcZ6tQrA9d4ctfNHrVvvA,4864
37
- treenode/static/treenode/css/treenode_admin.css,sha256=7Ye_bCgIgG-QUcih1jXIda1XxhAkTFLU-0CHcKNCZtw,2238
38
- treenode/static/treenode/js/.gitkeep,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
39
- treenode/static/treenode/js/tree_widget.js,sha256=SGxm2Awu1Ysk53h1r8JIS5wo9XGQFUD0cz9WqsMQXMs,10331
40
- treenode/static/treenode/js/treenode_admin.js,sha256=q_OvlDQPvmv9SfV2u69PztVY2Yrdl4qdlnoAzbbkovA,4747
41
- treenode/templates/.gitkeep,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
42
- treenode/templates/admin/.gitkeep,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
43
- treenode/templates/admin/export_success.html,sha256=xN2D-BCH249CJB10fo_vHYUyFenQ9mFKqq7UTWcrXS4,747
44
- treenode/templates/admin/tree_node_changelist.html,sha256=fGRVvWx2EnpiFYeJckyPKV-BCv9I13_ViiNN0LIUZKM,380
45
- treenode/templates/admin/tree_node_export.html,sha256=vJxEoGI-US6VdFddxAFgL5r3MgGt6mgA43vltCsbA2k,1043
46
- treenode/templates/admin/tree_node_import.html,sha256=unksxTAO2bJbxRkZfrCltHn61MgfqGt2sxIsUOW5dVk,1513
47
- treenode/templates/admin/tree_node_import_report.html,sha256=azHJ8JFrSRu60lF1Uh22zs9JXQxZdvOjYdwCtlbaE3I,1133
48
- treenode/templates/admin/treenode_ajax_rows.html,sha256=zFyPaTbSyxRjOqQ85SMv__qTIYDjEna6chYODBypDZA,224
49
- treenode/templates/widgets/tree_widget.css,sha256=2bEaxu1x7QJZ7erbs2SLMaxeaiMkjQXadfcDEW8wfok,551
50
- treenode/templates/widgets/tree_widget.html,sha256=GKcCU-B2FkkJ2BSOuXOw9e_PdYTtADcvyITEXqOlZ9Y,723
51
- treenode/utils/__init__.py,sha256=B4bv96ivtHELPv0_DllJa5z-k1QMo7z-MKuvj-3NdtI,356
52
- treenode/utils/aid.py,sha256=o8Jgc1vDRtQpx4XYdv0qR5Lqvens55Jfbdca1nr-EOA,1013
53
- treenode/utils/base16.py,sha256=U1PMit2aZOpYusG_u1c7eVpXO-cFrFPyVyk9zdHrehg,817
54
- treenode/utils/base36.py,sha256=yICmyPE-yyPNO9T2oALOt-b6uYf37ahFfx0R4tXn3X0,847
55
- treenode/utils/db.py,sha256=36q4OckKmEd6uHTbMTxdKpV9nOIZ55DAantRWR9bxWg,4297
56
- treenode/utils/exporter.py,sha256=mV6Gch7XfW8f_1x3WqWgtV0qekMLdo-_n9gz6GJjXjw,7259
57
- treenode/utils/importer.py,sha256=Hvirbd6NyZ2MHa56_jOrUF3kYFeby1DbSLR3mhHy-9s,12891
58
- treenode/utils/radix.py,sha256=zHpOuDxsebiv9Gza6snNhAtBKiex6CDrAVRtB6esaWo,1642
59
- django_fast_treenode-2.1.4.dist-info/LICENSE,sha256=T0evsb7y-63fg18ovdNSx3wwWWRwyluQvN9J4zFSvfE,1093
60
- django_fast_treenode-2.1.4.dist-info/METADATA,sha256=UoyzLdrnOMH4yEGblkK2SI8IK-mYu_lHqW5GDqOM-0U,8273
61
- django_fast_treenode-2.1.4.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
62
- django_fast_treenode-2.1.4.dist-info/top_level.txt,sha256=fmgxHbXyx1O2MPi_9kjx8aL9L-8TmV0gre4Go8XgqFk,9
63
- django_fast_treenode-2.1.4.dist-info/RECORD,,