django-fast-treenode 2.0.11__py3-none-any.whl → 2.1.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 (68) hide show
  1. {django_fast_treenode-2.0.11.dist-info → django_fast_treenode-2.1.0.dist-info}/LICENSE +2 -2
  2. django_fast_treenode-2.1.0.dist-info/METADATA +161 -0
  3. django_fast_treenode-2.1.0.dist-info/RECORD +75 -0
  4. {django_fast_treenode-2.0.11.dist-info → django_fast_treenode-2.1.0.dist-info}/WHEEL +1 -1
  5. treenode/admin/__init__.py +9 -0
  6. treenode/admin/admin.py +295 -0
  7. treenode/admin/changelist.py +65 -0
  8. treenode/admin/mixins.py +302 -0
  9. treenode/apps.py +12 -1
  10. treenode/cache.py +2 -2
  11. treenode/docs/.gitignore +0 -0
  12. treenode/docs/about.md +36 -0
  13. treenode/docs/admin.md +104 -0
  14. treenode/docs/api.md +739 -0
  15. treenode/docs/cache.md +187 -0
  16. treenode/docs/import_export.md +35 -0
  17. treenode/docs/index.md +30 -0
  18. treenode/docs/installation.md +74 -0
  19. treenode/docs/migration.md +145 -0
  20. treenode/docs/models.md +128 -0
  21. treenode/docs/roadmap.md +45 -0
  22. treenode/forms.py +8 -10
  23. treenode/managers/__init__.py +21 -0
  24. treenode/managers/adjacency.py +203 -0
  25. treenode/managers/closure.py +278 -0
  26. treenode/models/__init__.py +2 -1
  27. treenode/models/adjacency.py +343 -0
  28. treenode/models/classproperty.py +3 -0
  29. treenode/models/closure.py +23 -24
  30. treenode/models/factory.py +12 -2
  31. treenode/models/mixins/__init__.py +23 -0
  32. treenode/models/mixins/ancestors.py +65 -0
  33. treenode/models/mixins/children.py +81 -0
  34. treenode/models/mixins/descendants.py +66 -0
  35. treenode/models/mixins/family.py +63 -0
  36. treenode/models/mixins/logical.py +68 -0
  37. treenode/models/mixins/node.py +210 -0
  38. treenode/models/mixins/properties.py +156 -0
  39. treenode/models/mixins/roots.py +96 -0
  40. treenode/models/mixins/siblings.py +99 -0
  41. treenode/models/mixins/tree.py +344 -0
  42. treenode/signals.py +26 -0
  43. treenode/static/treenode/css/tree_widget.css +201 -31
  44. treenode/static/treenode/css/treenode_admin.css +48 -41
  45. treenode/static/treenode/js/tree_widget.js +269 -131
  46. treenode/static/treenode/js/treenode_admin.js +131 -171
  47. treenode/templates/admin/tree_node_changelist.html +6 -0
  48. treenode/templates/admin/treenode_ajax_rows.html +7 -0
  49. treenode/tests/tests.py +488 -0
  50. treenode/urls.py +10 -6
  51. treenode/utils/__init__.py +2 -0
  52. treenode/utils/aid.py +46 -0
  53. treenode/utils/base16.py +38 -0
  54. treenode/utils/base36.py +3 -1
  55. treenode/utils/db.py +116 -0
  56. treenode/utils/exporter.py +2 -0
  57. treenode/utils/importer.py +0 -1
  58. treenode/utils/radix.py +61 -0
  59. treenode/version.py +2 -2
  60. treenode/views.py +118 -43
  61. treenode/widgets.py +91 -43
  62. django_fast_treenode-2.0.11.dist-info/METADATA +0 -698
  63. django_fast_treenode-2.0.11.dist-info/RECORD +0 -42
  64. treenode/admin.py +0 -439
  65. treenode/docs/Documentation +0 -636
  66. treenode/managers.py +0 -419
  67. treenode/models/proxy.py +0 -669
  68. {django_fast_treenode-2.0.11.dist-info → django_fast_treenode-2.1.0.dist-info}/top_level.txt +0 -0
@@ -1,698 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: django-fast-treenode
3
- Version: 2.0.11
4
- Summary: Application for supporting tree (hierarchical) data structure in Django projects
5
- Home-page: https://github.com/TimurKady/django-fast-treenode
6
- Author: Timur Kady
7
- Author-email: Timur Kady <timurkady@yandex.com>
8
- License: MIT License
9
-
10
- Copyright (c) 2020-2023 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
- Project-URL: Homepage, https://github.com/TimurKady/django-fast-treenode
30
- Project-URL: Documentation, https://github.com/TimurKady/django-fast-treenode#readme
31
- Project-URL: Source, https://github.com/TimurKady/django-fast-treenode
32
- Project-URL: Issues, https://github.com/TimurKady/django-fast-treenode/issues
33
- Classifier: Development Status :: 5 - Production/Stable
34
- Classifier: Intended Audience :: Developers
35
- Classifier: Programming Language :: Python :: 3
36
- Classifier: Programming Language :: Python :: 3.9
37
- Classifier: Programming Language :: Python :: 3.10
38
- Classifier: Programming Language :: Python :: 3.11
39
- Classifier: Programming Language :: Python :: 3.12
40
- Classifier: Programming Language :: Python :: 3.13
41
- Classifier: Programming Language :: Python :: 3.14
42
- Classifier: Framework :: Django
43
- Classifier: Framework :: Django :: 4.0
44
- Classifier: Framework :: Django :: 4.1
45
- Classifier: Framework :: Django :: 4.2
46
- Classifier: Framework :: Django :: 5.0
47
- Classifier: Framework :: Django :: 5.1
48
- Classifier: Framework :: Django :: 5.2
49
- Classifier: License :: OSI Approved :: MIT License
50
- Classifier: Operating System :: OS Independent
51
- Requires-Python: >=3.9
52
- Description-Content-Type: text/markdown
53
- License-File: LICENSE
54
- Requires-Dist: Django>=4.0
55
- Requires-Dist: pympler>=1.0
56
- Requires-Dist: numpy>=2.0
57
- Requires-Dist: django-widget-tweaks>=1.5
58
- Provides-Extra: import-export
59
- Requires-Dist: openpyxl; extra == "import-export"
60
- Requires-Dist: pyyaml; extra == "import-export"
61
- Requires-Dist: xlsxwriter; extra == "import-export"
62
-
63
- # Django-fast-treenode
64
- __Combination of Adjacency List and Closure Table__
65
-
66
- ## Functions
67
- Application for supporting tree (hierarchical) data structure in Django projects
68
- * fast: the fastest of the two methods is used to process requests, combining the advantages of an **Adjacency Table** and a **Closure Table**,
69
- * even faster: the main resource-intensive operations are **cached**; **bulk operations** are used for inserts and changes,
70
- * synchronized: model instances in memory are automatically updated,
71
- * compatibility: you can easily add a tree node to existing projects using TreeNode without changing the code,
72
- * no dependencies,
73
- * easy setup: just extend the abstract model/model-admin,
74
- * admin integration: visualization options (accordion, breadcrumbs or padding),
75
- * widget: Built-in Select2-to-Tree extends Select2 to support arbitrary nesting levels.
76
-
77
- ## Debut idea
78
- This is a modification of the reusable [django-treenode](https://github.com/fabiocaccamo/django-treenode) application developed by [Fabio Caccamo](https://github.com/fabiocaccamo).
79
- The original application has significant advantages over other analogues, and indeed, is one of the best implementations of support for hierarchical structures for Django.
80
-
81
- Fabio's idea was to use the Adjacency List method to store the data tree. The most probable and time-consuming requests are calculated in advance and stored in the database. Also, most requests are cached. As a result, query processing is carried out in one call to the database or without it at all.
82
-
83
- However, this application has a number of undeniable shortcomings:
84
- * the selected pre-calculations scheme entails high costs for adding a new element;
85
- * inserting new elements uses signals, which leads to failures when using bulk-operations;
86
- * the problem of ordering elements by priority inside the parent node has not been resolved.
87
-
88
- My idea was to solve these problems by combining the adjacency list with the Closure Table. Main advantages:
89
- * the Closure Model is generated automatically;
90
- * maintained compatibility with the original package at the level of documented functions;
91
- * most requests are satisfied in one call to the database;
92
- * inserting a new element takes two calls to the database without signals usage;
93
- * bulk-operations are supported;
94
- * the cost of creating a new dependency is reduced many times;
95
- * useful functionality added for some methods (e.g. the `include_self=False` and `depth` parameters has been added to functions that return lists/querysets);
96
- * additionally, the package includes a tree view widget for the `tn_parent` field in the change form.
97
-
98
- Of course, at large levels of nesting, the use of the Closure Table leads to an increase in resource costs. However, the combined approach still outperforms both the original application and other available Django solutions in terms of performance, especially in large trees with over 100k nodes.
99
-
100
- ## Theory
101
- You can get a basic understanding of what is a Closure Table from:
102
- * [presentation](https://www.slideshare.net/billkarwin/models-for-hierarchical-data) by Bill Karwin;
103
- * [article](https://dirtsimple.org/2010/11/simplest-way-to-do-tree-based-queries.html) by blogger Dirt Simple;
104
- * [article](https://towardsdatascience.com/closure-table-pattern-to-model-hierarchies-in-nosql-c1be6a87e05b) by Andriy Zabavskyy.
105
-
106
- You can easily find additional information on your own on the Internet.
107
-
108
- ## Quick start
109
- 1. Run ```pip install django-fast-treenode```
110
- 2. Add ```treenode``` to ```settings.INSTALLED_APPS```
111
- 3. Make your model inherit from ```treenode.models.TreeNodeModel``` (described below)
112
- 4. Make your model-admin inherit from ```treenode.admin.TreeNodeModelAdmin``` (described below)
113
- 5. Run python manage.py makemigrations and ```python manage.py migrate```
114
-
115
- For more information on migrating from the **django-treenode** package and upgrading to version 2.0, see [below](#migration-guide).
116
-
117
- ## Configuration
118
- ### `models.py`
119
- Make your model class inherit from `treenode.models.TreeNodeModel`:
120
-
121
- ```python
122
- from django.db import models
123
- from treenode.models import TreeNodeModel
124
-
125
-
126
- class Category(TreeNodeModel):
127
-
128
- # the field used to display the model instance
129
- # default value 'pk'
130
- treenode_display_field = "name"
131
-
132
- name = models.CharField(max_length=50)
133
-
134
- class Meta(TreeNodeModel.Meta):
135
- verbose_name = "Category"
136
- verbose_name_plural = "Categories"
137
- ```
138
-
139
- The `TreeNodeModel` abstract class adds many fields (prefixed with `tn_` to prevent direct access) and public methods to your models.
140
-
141
- ### `admin.py`
142
- Make your model-admin class inherit from `treenode.admin.TreeNodeModelAdmin`.
143
-
144
- ```python
145
- from django.contrib import admin
146
-
147
- from treenode.admin import TreeNodeModelAdmin
148
- from treenode.forms import TreeNodeForm
149
-
150
- from .models import Category
151
-
152
-
153
- class CategoryAdmin(TreeNodeModelAdmin):
154
-
155
- # set the changelist display mode: 'accordion', 'breadcrumbs' or 'indentation' (default)
156
- # when changelist results are filtered by a querystring,
157
- # 'breadcrumbs' mode will be used (to preserve data display integrity)
158
- treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_ACCORDION
159
- # treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_BREADCRUMBS
160
- # treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_INDENTATION
161
-
162
- # use TreeNodeForm to automatically exclude invalid parent choices
163
- form = TreeNodeForm
164
-
165
- admin.site.register(Category, CategoryAdmin)
166
- ```
167
-
168
- ---
169
-
170
- ### `settings.py`
171
- You can use a custom cache backend by adding a `treenode` entry to `settings.CACHES`, otherwise the default cache backend will be used.
172
-
173
- ```python
174
- CACHES = {
175
- "default": {
176
- "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
177
- "LOCATION": "...",
178
- },
179
- "treenode": {
180
- "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
181
- "KEY_PREFIX": "", # This is important!
182
- "VERSION": None, # This is important!
183
- },
184
- }
185
- ```
186
- ### `forms.py`
187
-
188
- ```
189
- class YoursForm(TreeNodeForm):
190
- # Your code is here
191
- ```
192
-
193
-
194
- ## Usage
195
- ### Methods/Properties
196
-
197
- - [`delete`](#delete)
198
- - [`delete_tree`](#delete_tree)
199
- - [`get_ancestors`](#get_ancestors)
200
- - [`get_ancestors_count`](#get_ancestors_count)
201
- - [`get_ancestors_pks`](#get_ancestors_pks)
202
- - [`get_ancestors_queryset`](#get_ancestors_queryset)
203
- - [`get_breadcrumbs`](#get_breadcrumbs)
204
- - [`get_children`](#get_children)
205
- - [`get_children_count`](#get_children_count)
206
- - [`get_children_pks`](#get_children_pks)
207
- - [`get_children_queryset`](#get_children_queryset)
208
- - [`get_depth`](#get_depth)
209
- - [`get_descendants`](#get_descendants)
210
- - [`get_descendants_count`](#get_descendants_count)
211
- - [`get_descendants_pks`](#get_descendants_pks)
212
- - [`get_descendants_queryset`](#get_descendants_queryset)
213
- - [`get_descendants_tree`](#get_descendants_tree)
214
- - [`get_descendants_tree_display`](#get_descendants_tree_display)
215
- - [`get_first_child`](#get_first_child)
216
- - [`get_index`](#get_index)
217
- - [`get_last_child`](#get_last_child)
218
- - [`get_level`](#get_level)
219
- - [`get_order`](#get_order)
220
- - [`get_parent`](#get_parent)
221
- - [`get_parent_pk`](#get_parent_pk)
222
- - [`set_parent`](#set_parent)
223
- - [`get_path`](#get_path)
224
- - [`get_priority`](#get_priority)
225
- - [`set_priority`](#set_priority)
226
- - [`get_root`](#get_root)
227
- - [`get_root_pk`](#get_root_pk)
228
- - [`get_roots`](#get_roots)
229
- - [`get_roots_queryset`](#get_roots_queryset)
230
- - [`get_siblings`](#get_siblings)
231
- - [`get_siblings_count`](#get_siblings_count)
232
- - [`get_siblings_pks`](#get_siblings_pks)
233
- - [`get_siblings_queryset`](#get_siblings_queryset)
234
- - [`get_tree`](#get_tree)
235
- - [`get_tree_display`](#get_tree_display)
236
- - [`is_ancestor_of`](#is_ancestor_of)
237
- - [`is_child_of`](#is_child_of)
238
- - [`is_descendant_of`](#is_descendant_of)
239
- - [`is_first_child`](#is_first_child)
240
- - [`is_last_child`](#is_last_child)
241
- - [`is_leaf`](#is_leaf)
242
- - [`is_parent_of`](#is_parent_of)
243
- - [`is_root`](#is_root)
244
- - [`is_root_of`](#is_root_of)
245
- - [`is_sibling_of`](#is_sibling_of)
246
- - [`update_tree`](#update_tree)
247
-
248
- #### `delete`
249
- **Delete a node** provides two deletion strategies:
250
- - **Cascade Delete (`cascade=True`)**: Removes the node along with all its descendants.
251
- - **Reparenting (`cascade=False`)**: Moves the children of the deleted node up one level in the hierarchy before removing the node itself.
252
-
253
- ```python
254
- node.delete(cascade=True) # Deletes node and all its descendants
255
- node.delete(cascade=False) # Moves children up and then deletes the node
256
- ```
257
- This ensures greater flexibility in managing tree structures while preventing orphaned nodes.
258
-
259
- ---
260
-
261
- #### `delete_tree`
262
- **Delete the whole tree** for the current node class:
263
- ```python
264
- cls.delete_tree()
265
- ```
266
-
267
- #### `get_ancestors`
268
- Get a **list with all ancestors** (ordered from root to parent):
269
- ```python
270
- obj.get_ancestors(include_self=True, depth=None)
271
- # or
272
- obj.ancestors
273
- ```
274
-
275
- #### `get_ancestors_count`
276
- Get the **ancestors count**:
277
- ```python
278
- obj.get_ancestors_count(include_self=True, depth=None)
279
- # or
280
- obj.ancestors_count
281
- ```
282
-
283
- #### `get_ancestors_pks`
284
- Get the **ancestors pks** list:
285
- ```python
286
- obj.get_ancestors_pks(include_self=True, depth=None)
287
- # or
288
- obj.ancestors_pks
289
- ```
290
-
291
- #### `get_ancestors_queryset`
292
- Get the **ancestors queryset** (ordered from parent to root):
293
- ```python
294
- obj.get_ancestors_queryset(include_self=True, depth=None)
295
- ```
296
-
297
- #### `get_breadcrumbs`
298
- Get the **breadcrumbs** to current node (included):
299
- ```python
300
- obj.get_breadcrumbs(attr=None)
301
- # or
302
- obj.breadcrumbs
303
- ```
304
-
305
- #### `get_children`
306
- Get a **list containing all children**:
307
- ```python
308
- obj.get_children()
309
- # or
310
- obj.children
311
- ```
312
-
313
- #### `get_children_count`
314
- Get the **children count**:
315
- ```python
316
- obj.get_children_count()
317
- # or
318
- obj.children_count
319
- ```
320
-
321
- #### `get_children_pks`
322
- Get the **children pks** list:
323
- ```python
324
- obj.get_children_pks()
325
- # or
326
- obj.children_pks
327
- ```
328
-
329
- #### `get_children_queryset`
330
- Get the **children queryset**:
331
- ```python
332
- obj.get_children_queryset()
333
- ```
334
-
335
- #### `get_depth`
336
- Get the **node depth** (how many levels of descendants):
337
- ```python
338
- obj.get_depth()
339
- # or
340
- obj.depth
341
- ```
342
-
343
- #### `get_descendants`
344
- Get a **list containing all descendants**:
345
- ```python
346
- obj.get_descendants(include_self=False, depth=None)
347
- # or
348
- obj.descendants
349
- ```
350
-
351
- #### `get_descendants_count`
352
- Get the **descendants count**:
353
- ```python
354
- obj.get_descendants_count(include_self=False, depth=None)
355
- # or
356
- obj.descendants_count
357
- ```
358
-
359
- #### `get_descendants_pks`
360
- Get the **descendants pks** list:
361
- ```python
362
- obj.get_descendants_pks(include_self=False, depth=None)
363
- # or
364
- obj.descendants_pks
365
- ```
366
-
367
- #### `get_descendants_queryset`
368
- Get the **descendants queryset**:
369
- ```python
370
- obj.get_descendants_queryset(include_self=False, depth=None)
371
- ```
372
-
373
- #### `get_descendants_tree`
374
- Get a **n-dimensional** `dict` representing the **model tree**:
375
- ```python
376
- obj.get_descendants_tree()
377
- # or
378
- obj.descendants_tree
379
- ```
380
-
381
- **Important**: In future projects, avoid using `get_descendants_tree()`. It will be removed in the next version.
382
-
383
- #### `get_descendants_tree_display`
384
- Get a **multiline** `string` representing the **model tree**:
385
- ```python
386
- obj.get_descendants_tree_display(include_self=False, depth=None)
387
- # or
388
- obj.descendants_tree_display
389
- ```
390
-
391
- **Important**: In future projects, avoid using `get_descendants_tree_display()`. It will be removed in the next version.
392
-
393
- #### `get_first_child`
394
- Get the **first child node**:
395
- ```python
396
- obj.get_first_child()
397
- # or
398
- obj.first_child
399
- ```
400
-
401
- #### `get_index`
402
- Get the **node index** (index in node.parent.children list):
403
- ```python
404
- obj.get_index()
405
- # or
406
- obj.index
407
- ```
408
-
409
- #### `get_last_child`
410
- Get the **last child node**:
411
- ```python
412
- obj.get_last_child()
413
- # or
414
- obj.last_child
415
- ```
416
-
417
- #### `get_level`
418
- Get the **node level** (starting from 1):
419
- ```python
420
- obj.get_level()
421
- # or
422
- obj.level
423
- ```
424
-
425
- #### `get_order`
426
- Get the **order value** used for ordering:
427
- ```python
428
- obj.get_order()
429
- # or
430
- obj.order
431
- ```
432
-
433
- #### `get_parent`
434
- Get the **parent node**:
435
- ```python
436
- obj.get_parent()
437
- # or
438
- obj.parent
439
- ```
440
-
441
- #### `get_parent_pk`
442
- Get the **parent node pk**:
443
- ```python
444
- obj.get_parent_pk()
445
- # or
446
- obj.parent_pk
447
- ```
448
-
449
- #### `set_parent`
450
- Set the **parent node**:
451
- ```python
452
- obj.set_parent(parent_obj)
453
- ```
454
-
455
- #### `get_priority`
456
- Get the **node priority**:
457
- ```python
458
- obj.get_priority()
459
- # or
460
- obj.priority
461
- ```
462
- #### `get_path`
463
- Added the function of decorating a **materialized path**. The path is formed according to the value of the `tn_priority` field.
464
- ```python
465
- cls.get_path(prefix='', suffix='', delimiter='.', format_str='')
466
- ```
467
-
468
- #### `set_priority`
469
- Set the **node priority**:
470
- ```python
471
- obj.set_priority(100)
472
- ```
473
-
474
- #### `get_root`
475
- Get the **root node** for the current node:
476
- ```python
477
- obj.get_root()
478
- # or
479
- obj.root
480
- ```
481
-
482
- #### `get_root_pk`
483
- Get the **root node pk** for the current node:
484
- ```python
485
- obj.get_root_pk()
486
- # or
487
- obj.root_pk
488
- ```
489
-
490
- #### `get_roots`
491
- Get a **list with all root nodes**:
492
- ```python
493
- cls.get_roots()
494
- # or
495
- cls.roots
496
- ```
497
-
498
- #### `get_roots_queryset`
499
- Get **root nodes queryset**:
500
- ```python
501
- cls.get_roots_queryset()
502
- ```
503
-
504
- #### `get_siblings`
505
- Get a **list with all the siblings**:
506
- ```python
507
- obj.get_siblings()
508
- # or
509
- obj.siblings
510
- ```
511
-
512
- #### `get_siblings_count`
513
- Get the **siblings count**:
514
- ```python
515
- obj.get_siblings_count()
516
- # or
517
- obj.siblings_count
518
- ```
519
-
520
- #### `get_siblings_pks`
521
- Get the **siblings pks** list:
522
- ```python
523
- obj.get_siblings_pks()
524
- # or
525
- obj.siblings_pks
526
- ```
527
-
528
- #### `get_siblings_queryset`
529
- Get the **siblings queryset**:
530
- ```python
531
- obj.get_siblings_queryset()
532
- ```
533
-
534
- #### `get_tree`
535
- Returns an **n-dimensional dictionary** representing the model tree. Each node
536
- contains a "children"=[] key with a list of nested dictionaries of child nodes.:
537
- ```python
538
- cls.get_tree(instance=None)
539
- # or
540
- cls.tree
541
- ```
542
-
543
- #### `get_tree_display`
544
- Get a **multiline** `string` representing the **model tree**:
545
- ```python
546
- cls.get_tree_display()
547
- # or
548
- cls.tree_display
549
- ```
550
-
551
- #### `is_ancestor_of`
552
- Return `True` if the current node **is ancestor** of target_obj:
553
- ```python
554
- obj.is_ancestor_of(target_obj)
555
- ```
556
-
557
- #### `is_child_of`
558
- Return `True` if the current node **is child** of target_obj:
559
- ```python
560
- obj.is_child_of(target_obj)
561
- ```
562
-
563
- #### `is_descendant_of`
564
- Return `True` if the current node **is descendant** of target_obj:
565
- ```python
566
- obj.is_descendant_of(target_obj)
567
- ```
568
-
569
- #### `is_first_child`
570
- Return `True` if the current node is the **first child**:
571
- ```python
572
- obj.is_first_child()
573
- ```
574
-
575
- #### `is_last_child`
576
- Return `True` if the current node is the **last child**:
577
- ```python
578
- obj.is_last_child()
579
- ```
580
-
581
- #### `is_leaf`
582
- Return `True` if the current node is **leaf** (it has not children):
583
- ```python
584
- obj.is_leaf()
585
- ```
586
-
587
- #### `is_parent_of`
588
- Return `True` if the current node **is parent** of target_obj:
589
- ```python
590
- obj.is_parent_of(target_obj)
591
- ```
592
-
593
- #### `is_root`
594
- Return `True` if the current node **is root**:
595
- ```python
596
- obj.is_root()
597
- ```
598
-
599
- #### `is_root_of`
600
- Return `True` if the current node **is root** of target_obj:
601
- ```python
602
- obj.is_root_of(target_obj)
603
- ```
604
-
605
- #### `is_sibling_of`
606
- Return `True` if the current node **is sibling** of target_obj:
607
- ```python
608
- obj.is_sibling_of(target_obj)
609
- ```
610
-
611
- #### `update_tree`
612
- **Update tree** manually:
613
- ```python
614
- cls.update_tree()
615
- ```
616
- ## **Cache Management**
617
- ### **Overview**
618
- In v2.0, the caching mechanism has been improved to prevent excessive memory usage when multiple models inherit from `TreeNode`. The new system introduces **FIFO (First-In-First-Out) cache eviction**, with plans to test and integrate more advanced algorithms in future releases.
619
-
620
- ### **Key Features**
621
- **Global Cache Limit**: The setting `TREENODE_CACHE_LIMIT` defines the maximum cache size (in MB) for all models inheriting from `TreeNode`. Default is **100MB** if not explicitly set in `settings.py`.
622
- **settings.py**
623
- ``` python
624
- TREENODE_CACHE_LIMIT = 100
625
- ```
626
- **Automatic Management**. In most cases, users don’t need to manually manage cache operations.All methods that somehow change the state of models reset the tree cache automatically.
627
-
628
- **Manual Cache Clearing**. If for some reason you need to reset the cache, you can do it in two ways:
629
- - **Clear cache for a single model**: Use `clear_cache()` at the model level:
630
- ```python
631
- MyTreeNodeModel.clear_cache()
632
- ```
633
- - **Clear cache for all models**: Use the global `treenode_cache.clear()` method:
634
- ```python
635
- from treenode.cache import treenode_cache
636
- treenode_cache.clear()
637
- ```
638
-
639
- ## **Export and Import Functionality**
640
- ### **Overview**
641
- TreeNode v2.0 includes **built-in export and import features** for easier data migration. Supported Formats: `csv`, `json`, `xlsx`, `yaml`, `tsv`
642
- ### Installation for Import/Export Features
643
- By default, import/export functionality is **not included** to keep the package lightweight. If you need these features, install the package with:
644
- ```bash
645
- pip install django-fast-treenode[import_export]
646
- ```
647
- Once installed, **import/export buttons will appear** in the Django admin interface.
648
- ### **Important Considerations**
649
- Exporting objects with M2M fields may lead to serialization issues. Some formats (e.g., CSV) do not natively support many-to-many relationships. If you encounter errors, consider exporting data in `json` or `yaml` format, which better handle nested structures.
650
-
651
- ## Migration Guide
652
- #### Switching from `django-treenode`
653
- The migration process from `django-treenode` is fully automated. No manual steps are required. Upon upgrading, the necessary data structures will be checked and updated automatically. In exceptional cases, you can call the update code `cls.update_tree()` manually.
654
-
655
-
656
- #### Upgrading to `django-fast-treenode` 2.0
657
- To upgrade to version 2.0, simply run:
658
- ```bash
659
- pip install --upgrade django-fast-treenode
660
- ```
661
- or
662
- ```bash
663
- pip install django-fast-treenode[import_export]
664
- ```
665
- After upgrading, ensure that your database schema is up to date by running:
666
- ```bash
667
- python manage.py makemigrations
668
- python manage.py migrate
669
- ```
670
- This will apply any necessary database changes automatically.
671
-
672
-
673
- ## To do
674
- These improvements aim to enhance usability, performance, and maintainability for all users of `django-fast-treenode`:
675
- * **Cache Algorithm Optimization**: Testing and integrating more advanced cache eviction strategies.
676
- * **Drag-and-Drop UI Enhancements**: Adding intuitive drag-and-drop functionality for tree node management.
677
- * to be happy, to don't worry, until die.
678
-
679
- Your wishes, objections, comments are welcome.
680
-
681
-
682
- # Django-fast-treenode
683
-
684
- ## License
685
- Released under [MIT License](https://github.com/TimurKady/django-fast-treenode/blob/main/LICENSE).
686
-
687
- ## Cautions
688
- **Warning**: Do not access the tree node fields directly! Most of *django-treenode* model fields have been removed as unnecessary. Now only `tn_parent` and `tn_priority` are supported and will be kept in the future.
689
-
690
- **Risks of Direct Field Access:**
691
- - **Database Integrity Issues**: Directly modifying fields may break tree integrity, causing inconsistent parent-child relationships.
692
- - **Loss of Cached Data**: The caching system relies on controlled updates. Bypassing methods like `set_parent()` or `update_tree()` may lead to outdated or incorrect data.
693
- - **Unsupported Behavior**: Future versions may change field structures or remove unnecessary fields. Relying on them directly risks breaking compatibility.
694
-
695
- Instead, always use the **documented methods** described above or refer to the [original application documentation](https://github.com/fabiocaccamo/django-treenode).
696
-
697
- ## Credits
698
- This software contains, uses, and includes, in a modified form, [django-treenode](https://github.com/fabiocaccamo/django-treenode) by [Fabio Caccamo](https://github.com/fabiocaccamo). Special thanks to [Mathieu Leplatre](https://blog.mathieu-leplatre.info/pages/about.html) for the advice used in writing this application.