django-fast-treenode 2.1.2__tar.gz → 2.1.3__tar.gz

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 (86) hide show
  1. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/MANIFEST.in +1 -0
  2. {django_fast_treenode-2.1.2/django_fast_treenode.egg-info → django_fast_treenode-2.1.3}/PKG-INFO +5 -1
  3. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/README.md +4 -0
  4. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3/django_fast_treenode.egg-info}/PKG-INFO +5 -1
  5. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/django_fast_treenode.egg-info/SOURCES.txt +1 -1
  6. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/about.md +19 -12
  7. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/admin.md +6 -2
  8. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/api.md +25 -20
  9. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/cache.md +7 -0
  10. django_fast_treenode-2.1.3/docs/index.md +95 -0
  11. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/migration.md +1 -1
  12. django_fast_treenode-2.1.3/docs/roadmap.md +63 -0
  13. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/pyproject.toml +2 -1
  14. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/setup.cfg +1 -1
  15. django_fast_treenode-2.1.2/treenode/tests/tests.py → django_fast_treenode-2.1.3/tests/test_suite.py +58 -50
  16. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/cache.py +1 -1
  17. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/node.py +1 -1
  18. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/version.py +1 -1
  19. django_fast_treenode-2.1.2/docs/index.md +0 -30
  20. django_fast_treenode-2.1.2/docs/roadmap.md +0 -45
  21. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/LICENSE +0 -0
  22. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/django_fast_treenode.egg-info/dependency_links.txt +0 -0
  23. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/django_fast_treenode.egg-info/requires.txt +0 -0
  24. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/django_fast_treenode.egg-info/top_level.txt +0 -0
  25. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/.gitignore +0 -0
  26. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/.nojekyll +0 -0
  27. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/import_export.md +0 -0
  28. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/installation.md +0 -0
  29. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/models.md +0 -0
  30. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/docs/requirements.txt +0 -0
  31. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/setup.py +0 -0
  32. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/__init__.py +0 -0
  33. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/admin/__init__.py +0 -0
  34. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/admin/admin.py +0 -0
  35. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/admin/changelist.py +0 -0
  36. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/admin/mixins.py +0 -0
  37. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/apps.py +0 -0
  38. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/forms.py +0 -0
  39. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/managers/__init__.py +0 -0
  40. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/managers/adjacency.py +0 -0
  41. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/managers/closure.py +0 -0
  42. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/__init__.py +0 -0
  43. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/adjacency.py +0 -0
  44. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/classproperty.py +0 -0
  45. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/closure.py +0 -0
  46. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/factory.py +0 -0
  47. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/__init__.py +0 -0
  48. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/ancestors.py +0 -0
  49. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/children.py +0 -0
  50. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/descendants.py +0 -0
  51. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/family.py +0 -0
  52. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/logical.py +0 -0
  53. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/properties.py +0 -0
  54. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/roots.py +0 -0
  55. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/siblings.py +0 -0
  56. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/models/mixins/tree.py +0 -0
  57. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/signals.py +0 -0
  58. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/static/.gitkeep +0 -0
  59. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/static/treenode/.gitkeep +0 -0
  60. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/static/treenode/css/.gitkeep +0 -0
  61. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/static/treenode/css/tree_widget.css +0 -0
  62. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/static/treenode/css/treenode_admin.css +0 -0
  63. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/static/treenode/js/.gitkeep +0 -0
  64. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/static/treenode/js/tree_widget.js +0 -0
  65. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/static/treenode/js/treenode_admin.js +0 -0
  66. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/templates/.gitkeep +0 -0
  67. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/templates/admin/.gitkeep +0 -0
  68. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/templates/admin/export_success.html +0 -0
  69. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/templates/admin/tree_node_changelist.html +0 -0
  70. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/templates/admin/tree_node_export.html +0 -0
  71. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/templates/admin/tree_node_import.html +0 -0
  72. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/templates/admin/tree_node_import_report.html +0 -0
  73. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/templates/admin/treenode_ajax_rows.html +0 -0
  74. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/templates/widgets/tree_widget.css +0 -0
  75. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/templates/widgets/tree_widget.html +0 -0
  76. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/urls.py +0 -0
  77. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/utils/__init__.py +0 -0
  78. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/utils/aid.py +0 -0
  79. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/utils/base16.py +0 -0
  80. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/utils/base36.py +0 -0
  81. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/utils/db.py +0 -0
  82. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/utils/exporter.py +0 -0
  83. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/utils/importer.py +0 -0
  84. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/utils/radix.py +0 -0
  85. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/views.py +0 -0
  86. {django_fast_treenode-2.1.2 → django_fast_treenode-2.1.3}/treenode/widgets.py +0 -0
@@ -3,3 +3,4 @@ include README.md
3
3
  recursive-include treenode/static *
4
4
  recursive-include treenode/templates *
5
5
  recursive-include docs *
6
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: django-fast-treenode
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: Application for supporting tree (hierarchical) data structure in Django projects
5
5
  Home-page: https://django-fast-treenode.readthedocs.io/
6
6
  Author: Timur Kady
@@ -63,6 +63,10 @@ Requires-Dist: xlsxwriter; extra == "import-export"
63
63
  # Django-fast-treenode
64
64
  **Combining Adjacency List and Closure Table for Optimal Performance**
65
65
 
66
+ [![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)
67
+ [![Docs](https://readthedocs.org/projects/django-fast-treenode/badge/?version=latest)](https://django-fast-treenode.readthedocs.io/)
68
+ [![PyPI](https://img.shields.io/pypi/v/django-fast-treenode.svg)](https://pypi.org/project/django-fast-treenode/)
69
+ [![Published on Django Packages](https://img.shields.io/badge/Published%20on-Django%20Packages-0c3c26)](https://djangopackages.org/packages/p/django-fast-treenode/)
66
70
 
67
71
  **Django Fast TreeNode** is a high-performance Django application for working with tree structures, combining **Adjacency List** and **Closure Table** models. Each **TreeNodeModel** instance maintains two synchronized tables, enabling most operations to be performed with a single database query.
68
72
 
@@ -1,6 +1,10 @@
1
1
  # Django-fast-treenode
2
2
  **Combining Adjacency List and Closure Table for Optimal Performance**
3
3
 
4
+ [![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)
5
+ [![Docs](https://readthedocs.org/projects/django-fast-treenode/badge/?version=latest)](https://django-fast-treenode.readthedocs.io/)
6
+ [![PyPI](https://img.shields.io/pypi/v/django-fast-treenode.svg)](https://pypi.org/project/django-fast-treenode/)
7
+ [![Published on Django Packages](https://img.shields.io/badge/Published%20on-Django%20Packages-0c3c26)](https://djangopackages.org/packages/p/django-fast-treenode/)
4
8
 
5
9
  **Django Fast TreeNode** is a high-performance Django application for working with tree structures, combining **Adjacency List** and **Closure Table** models. Each **TreeNodeModel** instance maintains two synchronized tables, enabling most operations to be performed with a single database query.
6
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: django-fast-treenode
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: Application for supporting tree (hierarchical) data structure in Django projects
5
5
  Home-page: https://django-fast-treenode.readthedocs.io/
6
6
  Author: Timur Kady
@@ -63,6 +63,10 @@ Requires-Dist: xlsxwriter; extra == "import-export"
63
63
  # Django-fast-treenode
64
64
  **Combining Adjacency List and Closure Table for Optimal Performance**
65
65
 
66
+ [![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)
67
+ [![Docs](https://readthedocs.org/projects/django-fast-treenode/badge/?version=latest)](https://django-fast-treenode.readthedocs.io/)
68
+ [![PyPI](https://img.shields.io/pypi/v/django-fast-treenode.svg)](https://pypi.org/project/django-fast-treenode/)
69
+ [![Published on Django Packages](https://img.shields.io/badge/Published%20on-Django%20Packages-0c3c26)](https://djangopackages.org/packages/p/django-fast-treenode/)
66
70
 
67
71
  **Django Fast TreeNode** is a high-performance Django application for working with tree structures, combining **Adjacency List** and **Closure Table** models. Each **TreeNodeModel** instance maintains two synchronized tables, enabling most operations to be performed with a single database query.
68
72
 
@@ -22,6 +22,7 @@ docs/migration.md
22
22
  docs/models.md
23
23
  docs/requirements.txt
24
24
  docs/roadmap.md
25
+ tests/test_suite.py
25
26
  treenode/__init__.py
26
27
  treenode/apps.py
27
28
  treenode/cache.py
@@ -72,7 +73,6 @@ treenode/templates/admin/tree_node_import_report.html
72
73
  treenode/templates/admin/treenode_ajax_rows.html
73
74
  treenode/templates/widgets/tree_widget.css
74
75
  treenode/templates/widgets/tree_widget.html
75
- treenode/tests/tests.py
76
76
  treenode/utils/__init__.py
77
77
  treenode/utils/aid.py
78
78
  treenode/utils/base16.py
@@ -1,31 +1,36 @@
1
1
  ## About the project
2
2
  ### The Debut Idea
3
+
3
4
  The idea of ​​this package belongs to **[Fabio Caccamo](https://github.com/fabiocaccamo)**. His 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.
4
5
 
5
6
  The original application **[django-treenode](https://github.com/fabiocaccamo/django-treenode)** has significant advantages over other analogues, and indeed, is one of the best implementations of support for hierarchical structures for Django.
6
7
 
7
8
  However, this application has a number of undeniable shortcomings:
8
- * the selected pre-calculations scheme entails high costs for adding a new element;
9
- * inserting new elements uses signals, which leads to failures when using bulk-operations;
10
- * the problem of ordering elements by priority inside the parent node has not been resolved.
9
+
10
+ - the selected pre-calculations scheme entails high costs for adding a new element;
11
+ - inserting new elements uses signals, which leads to failures when using bulk-operations;
12
+ - the problem of ordering elements by priority inside the parent node has not been resolved.
11
13
 
12
14
  That is, an excellent debut idea, in my humble opinion, should have been improved.
13
15
 
14
16
  ### The Development of the Idea
17
+
15
18
  My idea was to solve these problems by combining the **Adjacency List** with the **Closure Table**. Main advantages:
16
- * the Closure Model is generated automatically;
17
- * maintained compatibility with the original package at the level of documented functions;
18
- * most requests are satisfied in one call to the database;
19
- * inserting a new element takes two calls to the database without signals usage;
20
- * bulk-operations are supported;
21
- * the cost of creating a new dependency is reduced many times;
22
- * useful functionality added for some methods (e.g. the `include_self=False` and `depth` parameters has been added to functions that return lists/querysets);
23
- * additionally, the package includes a tree view widget for the `tn_parent` field in the change form.
19
+
20
+ - the Closure Model is generated automatically;
21
+ - maintained compatibility with the original package at the level of documented functions;
22
+ - most requests are satisfied in one call to the database;
23
+ - inserting a new element takes two calls to the database without signals usage;
24
+ - bulk-operations are supported;
25
+ - the cost of creating a new dependency is reduced many times;
26
+ - useful functionality added for some methods (e.g. the `include_self=False` and `depth` parameters has been added to functions that return lists/querysets);
27
+ - additionally, the package includes a tree view widget for the `tn_parent` field in the change form.
24
28
 
25
29
  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.
26
30
 
27
31
  ### The Theory
28
32
  You can get a basic understanding of what is a **Closure Table** from:
33
+
29
34
  * [presentation](https://www.slideshare.net/billkarwin/models-for-hierarchical-data) by **Bill Karwin**;
30
35
  * [article](https://dirtsimple.org/2010/11/simplest-way-to-do-tree-based-queries.html) by blogger **Dirt Simple**;
31
36
  * [article](https://towardsdatascience.com/closure-table-pattern-to-model-hierarchies-in-nosql-c1be6a87e05b) by **Andriy Zabavskyy**.
@@ -33,4 +38,6 @@ You can get a basic understanding of what is a **Closure Table** from:
33
38
  You can easily find additional information on your own on the Internet.
34
39
 
35
40
  ### Our days
36
- Over the course of development, the package has undergone significant improvements, with a strong emphasis on performance optimization, database efficiency, and seamless integration with Django’s admin interface. The introduction of a hybrid model combining **Adjacency List** and **Closure Table** has substantially reduced query overhead, improved scalability, and enhanced flexibility when managing hierarchical data. These advancements have made the package not only a powerful but also a practical solution for working with large tree structures. Moving forward, the project will continue to evolve, focusing on refining caching mechanisms, expanding compatibility with Django’s ecosystem, and introducing further optimizations to ensure maximum efficiency and ease of use.
41
+ Over the course of development, the package has undergone significant improvements, with a strong emphasis on performance optimization, database efficiency, and seamless integration with Django’s admin interface.
42
+
43
+ The introduction of a hybrid model combining **Adjacency List** and **Closure Table** has substantially reduced query overhead, improved scalability, and enhanced flexibility when managing hierarchical data. These advancements have made the package not only a powerful but also a practical solution for working with large tree structures. Moving forward, the project will continue to evolve, focusing on refining caching mechanisms, expanding compatibility with Django’s ecosystem, and introducing further optimizations to ensure maximum efficiency and ease of use.
@@ -25,6 +25,7 @@ class CategoryAdmin(TreeNodeModelAdmin):
25
25
  The tree structure in the admin panel **loads dynamically as nodes are expanded**. This allows handling **large datasets** efficiently, preventing performance issues.
26
26
 
27
27
  You can choose from three display modes:
28
+
28
29
  - **`TREENODE_DISPLAY_MODE_ACCORDION` (default)**
29
30
  Expands/collapses nodes dynamically.
30
31
  - **`TREENODE_DISPLAY_MODE_BREADCRUMBS`**
@@ -43,10 +44,11 @@ The search bar helps quickly locate nodes within large trees. As you type, **an
43
44
 
44
45
  #### Using TreeNodeForm
45
46
  If you need to customize forms for tree-based models, inherit from `TreeNodeForm`. It provides:
47
+
46
48
  - A **custom tree widget** for selecting parent nodes.
47
49
  - Automatic **exclusion of self and descendants** from the parent selection to prevent circular references.
48
50
 
49
- ##### `forms.py`:
51
+ ##### forms.py:
50
52
  ```python
51
53
  from treenode.forms import TreeNodeForm
52
54
  from .models import Category
@@ -59,6 +61,7 @@ class CategoryForm(TreeNodeForm):
59
61
  ```
60
62
 
61
63
  Key Considerations:
64
+
62
65
  - This form automatically ensures that **a node cannot be its own parent**.
63
66
  - It uses **`TreeWidget`**, a custom hierarchical dropdown for selecting parent nodes.
64
67
  - If you need a form for another tree-based model, use the **dynamic factory method**:
@@ -75,7 +78,7 @@ This method ensures that the form correctly associates with different tree model
75
78
  #### The TreeWidget Class
76
79
  The `TreeWidget` class is a **custom Select2-like widget** that enables hierarchical selection in forms. While it is used inside the Django admin panel by default, it can **also be used in regular forms** outside the admin panel.
77
80
 
78
- ##### `widgets.py`
81
+ ##### widgets.py
79
82
 
80
83
  ```python
81
84
  from django import forms
@@ -91,6 +94,7 @@ class CategorySelectionForm(forms.Form):
91
94
  ```
92
95
 
93
96
  Important Notes:
97
+
94
98
  - **Requires jQuery**: The widget relies on AJAX requests, so ensure jQuery is available when using it outside Django’s admin.
95
99
  - **Dynamically Fetches Data**: It loads the tree structure asynchronously, preventing performance issues with large datasets.
96
100
  - **Customizable Data Source**: The `data-url` attribute can be adjusted to fetch tree data from a custom endpoint.
@@ -94,6 +94,7 @@ These methods are designed to manage **direct child nodes** within the tree stru
94
94
  obj.add_child(position=None, **kwargs)
95
95
  ```
96
96
  `position` specifies the order position of the object being added in the list of children of this node. It can be `'first-child'`, `'last-child'`, `'sorted-child'`, or an integer value.
97
+
97
98
  The `**kwargs` parameters contain the object creation data that will be passed to the inherited node model. Instead of passing the object creation data, you can pass an already created (but not yet saved) model instance to insert into the tree using the `instance` keyword.
98
99
 
99
100
  ```python
@@ -201,10 +202,10 @@ obj.get_family()
201
202
  ### Node Utility Methods
202
203
  This set of methods helps manage node-related operations such as:
203
204
 
204
- - **Breadcrumbs generation**
205
- - **Depth, index, and level calculations**
206
- - **Materialized path retrieval for sorting**
207
- - **Dynamic node positioning within the tree**
205
+ - Breadcrumbs generation
206
+ - Depth, index, and level calculations
207
+ - Materialized path retrieval for sorting
208
+ - Dynamic node positioning within the tree
208
209
 
209
210
  #### get_breadcrumbs
210
211
  Returns the **breadcrumbs** to current node (included):
@@ -246,19 +247,20 @@ obj.insert_at(target, position='first-child', save=False)
246
247
  ```
247
248
 
248
249
  Parameters:
250
+
249
251
  - `target`: еhe target node relative to which this node will be placed.
250
252
  - `position`: the position, relative to the target node, where the current node object will be moved to, can be one of:
251
- - `first-root`: the node will be the first root node;
252
- - `last-root`: the node will be the last root node;
253
- - `sorted-root`: the new node will be moved after sorting by the treenode_sort_field field;
254
- - `first-sibling`: the node will be the new leftmost sibling of the target node;
255
- - `left-sibling`: the node will take the target node’s place, which will be moved to the target position with shifting follows nodes;
256
- - `right-sibling`: the node will be moved to the position after the target node;
257
- - `last-sibling`: the node will be the new rightmost sibling of the target node;
258
- - `sorted-sibling`: the new node will be moved after sorting by the treenode_sort_field field;
259
- - first-child: the node will be the first child of the target node;
260
- - last-child: the node will be the new rightmost child of the target
261
- - sorted-child: the new node will be moved after sorting by the treenode_sort_field field.
253
+ - `first-root`: the node will be the first root node;
254
+ - `last-root`: the node will be the last root node;
255
+ - `sorted-root`: the new node will be moved after sorting by the treenode_sort_field field;
256
+ - `first-sibling`: the node will be the new leftmost sibling of the target node;
257
+ - `left-sibling`: the node will take the target node’s place, which will be moved to the target position with shifting follows nodes;
258
+ - `right-sibling`: the node will be moved to the position after the target node;
259
+ - `last-sibling`: the node will be the new rightmost sibling of the target node;
260
+ - `sorted-sibling`: the new node will be moved after sorting by the treenode_sort_field field;
261
+ - first-child: the node will be the first child of the target node;
262
+ - last-child: the node will be the new rightmost child of the target
263
+ - sorted-child: the new node will be moved after sorting by the treenode_sort_field field.
262
264
  - `save` : if `save=true`, the node will be saved in the tree. Otherwise, the method will return a model instance with updated fields: parent field and position in sibling list.
263
265
 
264
266
  Before using this method, the model instance must be correctly created with all required fields defined. If the model has required fields, then simply creating an object and calling insert_at() will not work, because Django will raise an exception.
@@ -268,9 +270,11 @@ Moves the model instance relative to the target node and sets its position (if n
268
270
  ```python
269
271
  obj.move_to(target, position=0)
270
272
  ```
273
+
271
274
  Parameters:
272
- - `target`: еhe target node relative to which this node will be placed.
273
- - `position` the position, relative to the target node, where the current node object will be moved to. For detals see [insert_at](#insert_at) method.
275
+
276
+ - `target`: the target node relative to which this node will be placed.
277
+ - `position`: the position, relative to the target node, where the current node object will be moved to. For detals see [insert_at](#insert_at) method.
274
278
 
275
279
  #### get_path
276
280
  Returns Materialized Path of node. The materialized path is constructed by recording the position of each node within its parent's list of children, tracing this sequence back through all its ancestors.
@@ -538,6 +542,7 @@ Something like this will be returned:
538
542
  All nodes are ordered by materialized path.
539
543
 
540
544
  This can be used with a template like this:
545
+
541
546
  ```html
542
547
  {% for item, info in annotated_list %}
543
548
  {% if info.open %}
@@ -546,7 +551,7 @@ This can be used with a template like this:
546
551
  </li><li>
547
552
  {% endif %}
548
553
 
549
- {{ item }}
554
+ {{ item }}
550
555
 
551
556
  {% for close in info.close %}
552
557
  </li></ul>
@@ -663,7 +668,7 @@ Returns the children pks list. See [`get_children_pks()`](#get_children_pks) met
663
668
  #### obj.depth
664
669
  Returns the node depth. See [`get_depth()`](#get_depth) method.
665
670
 
666
- ### obj.descendants:
671
+ #### obj.descendants:
667
672
  Returns a list containing all descendants (itself is not included). See [`get_descendants()`](#get_descendants) method.
668
673
 
669
674
  #### obj.descendants_count
@@ -693,7 +698,7 @@ Returns node parent pk. See [`get_parent_pk()`](#get_parent_pk) method.
693
698
  #### obj.priority
694
699
  Returns node oder position (priority). See [`get_priority()`](#get_priority) method.
695
700
 
696
- ### cls.roots
701
+ #### cls.roots
697
702
  Returns a list with all root nodes. See [`get_roots()`](#get_roots) method.
698
703
 
699
704
  #### obj.root
@@ -27,6 +27,7 @@ TREENODE_CACHE_LIMIT = 100
27
27
  **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.
28
28
 
29
29
  **Manual Cache Clearing**. If for some reason you need to reset the cache, you can do it in two ways:
30
+
30
31
  - **Clear cache for a single model**: Use `clear_cache()` at the model level:
31
32
  ```python
32
33
  MyTreeNodeModel.clear_cache()
@@ -116,6 +117,7 @@ treenode_cache.clear()
116
117
  This completely resets the cache, removing **all stored values**.
117
118
 
118
119
  Best Practices:
120
+
119
121
  - **Always use `generate_cache_key()`** instead of hardcoding cache keys to ensure consistency.
120
122
  - **Use `invalidate()` instead of `clear()`** when targeting a specific model’s cache.
121
123
  - **Apply `@cached_method` wisely**, ensuring it is used **only for** `TreeNodeModel`-based methods to avoid conflicts.
@@ -137,6 +139,7 @@ The cache is now divided into two queues:
137
139
  - **LRU (20%-30%)** – for frequently accessed objects (Least Recently Used).
138
140
 
139
141
  How it works:
142
+
140
143
  - If tree queries are evenly distributed, the cache behaves like a standard FIFO.
141
144
  - If certain nodes receive frequent queries, those records automatically move to LRU, reducing database load.
142
145
 
@@ -145,10 +148,12 @@ How it works:
145
148
  Each new entry initially goes into **FIFO**. If accessed **more than N times**, it moves to **LRU**, where it remains longer.
146
149
 
147
150
  **How N is determined:**
151
+
148
152
  - A threshold is set based on **mathematical statistics**: N = 1 / sqrt(2).
149
153
  - An absolute threshold limit is added.
150
154
 
151
155
  **LRU Behavior:**
156
+
152
157
  - When accessed, a record moves to the top of the list.
153
158
  - New records are also placed at the top.
154
159
  - If LRU reaches capacity, **the evicted record returns to FIFO** instead of being deleted.
@@ -162,11 +167,13 @@ When data is modified or deleted, the cache **automatically resets** to prevent
162
167
  To automatically clear outdated records, an **adaptive mechanism** is used. Instead of a static TTL, the **DTT parameter** is dynamically calculated based on **Poisson distribution**.
163
168
 
164
169
  How DTT is calculated:
170
+
165
171
  1. Compute the **average interval between queries (T)**: ``` T = (1/N) * Σ (t_i - t_(i-1)), i=1...N```
166
172
  2. Store **averaged value** `ΣΔt / N`
167
173
  3. Set **DTT = 3T**, which removes **95% of infrequent queries**.
168
174
 
169
175
  **Why this is better than a fixed TTL:**
176
+
170
177
  - If queries are rare → DTT **increases**, preventing premature deletions.
171
178
  - If queries are frequent → DTT **decreases**, accelerating cache clearing.
172
179
 
@@ -0,0 +1,95 @@
1
+ # Django Fast TreeNode
2
+ **A Powerful Solution for Hierarchical Data Management**
3
+
4
+ Welcome to the **django-fast-treenode** documentation!
5
+
6
+ ## Overview
7
+
8
+ **Django Fast TreeNode** is a specialized Django package designed to efficiently handle hierarchical data structures. It leverages **combining Adjacency List and Closure Table** to optimize querying and management of tree-like structures, making it an excellent choice for applications that require fast traversal, retrieval, and manipulation of nested relationships.
9
+
10
+ The package is particularly useful in scenarios such as:
11
+
12
+ - **Categories and taxonomies**: Manage product categories, tags, and classification systems.
13
+ - **Menus and navigation**: Create tree-like menus and nested navigation structures.
14
+ - **Forums and comments**: Store threaded discussions and nested comment chains.
15
+ - **Geographical data**: Represent administrative divisions, regions, and areas of influence.
16
+ - **Organizational and Business Structures**: Model company hierarchies, business processes, employees and departments.
17
+
18
+ **django-fast-treenode** models show excellent **performance** and **stability** in all applications.
19
+
20
+ ## Key Features
21
+
22
+ 1. **Efficient Tree Operations**:
23
+ * Supports fast retrieval of ancestors, descendants, and siblings.
24
+ * Uses a **closure table approach** for optimal query performance.
25
+
26
+ 2. **Django ORM Integration**:
27
+ * Extends Django’s `models.Model`, making it easy to integrate into existing applications.
28
+ * Custom manager (`TreeNodeManager`) provides useful tree-related query optimizations.
29
+
30
+
31
+ 3. **Query Optimization via Closure Table**
32
+ * Instead of naive recursive queries, the package precomputes relationships in a separate closure table.This allows you to run constant-time queries for retrieval using a **single database query**:
33
+ * Ancestors (`get_ancestors()`),
34
+ * Depth (`det_depth()`),
35
+ * Descendants (`get_descendants()`),
36
+ * Family (`get_family()`),
37
+ * Materialized Path(`get_breadcrumbs()` and `get_path()`),
38
+ * Roots (`get_root()`),
39
+ * Siblings (`get_siblings()`) and so on.
40
+
41
+ 4. **Automatic Ordering and Priority Management**:
42
+ * Nodes can be assigned priority values for custom sorting.
43
+ * Provides automatic ordering based on a **materialized path**.
44
+
45
+ 5. **Admin Interface Enhancements**:
46
+ * Supports multiple tree display modes in the Django Admin:
47
+ * **Indentation mode** (classic hierarchical view)
48
+ * **Breadcrumbs mode** (for easy navigation)
49
+ * **Accordion mode** (collapsible structures)
50
+ * Uses a **custom admin widget** (`TreeWidget`) to enhance usability.
51
+
52
+
53
+ 6. **Caching for Performance**:
54
+ * Uses Django’s caching framework to optimize frequently accessed tree operations.
55
+ * Cached tree methods reduce redundant computations.
56
+
57
+
58
+ 7. **Bulk Operations Support**:
59
+ * Implements efficient **bulk creation** of nodes.
60
+ * Provides methods for **batch updates** and **tree rebuilding**.
61
+
62
+ By leveraging combining with Closure Tables, it offers superior performance compared to traditional tree structures.
63
+
64
+ ## Use Cases and Benefits
65
+
66
+ ### When to Use Django Fast TreeNode?
67
+ - **You need a nested structure with frequent reads**: Closure tables provide **fast lookups** compared to recursive Common Table Expressions (CTEs).
68
+ - **You want an easy-to-use Django model**: The package **extends Django ORM** and integrates seamlessly.
69
+ - **You require hierarchical display in Django Admin**: The **custom admin integration** makes managing trees much easier.
70
+
71
+ ### Benefits Over Other Approaches
72
+ - **Better than adjacency lists** (which require multiple queries for deep trees).
73
+ - **More efficient than recursive queries** (which can be slow on large datasets).
74
+ - **Scalable for large trees** (by leveraging precomputed paths and caching).
75
+
76
+ Django Fast TreeNode is a powerful and efficient package for managing hierarchical data in Django applications. Its seamless ORM integration and Django Admin support make it a great choice for developers looking to implement **fast, scalable** tree-based data structures.
77
+
78
+ ## Contents
79
+ - [About the Project](about.md)
80
+ - [Installation, Configuration, and Fine-Tuning](installation.md)
81
+ - [Model Inheritance and Extensions](models.md)
82
+ - [Working with Admin Classes](admin.md)
83
+ - [API Reference](api.md)
84
+ - [Import & Export](import_export.md)
85
+ - [Caching and Cache Management](cache.md)
86
+ - [Migration and Upgrade Guide](migration.md)
87
+ - [Roadmap](roadmap.md)
88
+
89
+ ## Links
90
+ - [Issues](https://github.com/TimurKady/django-fast-treenode/issues)
91
+ - [Pull Requests](https://github.com/TimurKady/django-fast-treenode/pulls)
92
+ - [Discussions](https://github.com/TimurKady/django-fast-treenode/discussions)
93
+
94
+ ## License
95
+ Released under the [MIT License](https://github.com/TimurKady/django-fast-treenode/blob/main/LICENSE).
@@ -111,7 +111,7 @@ The `django-fast-treenode` package contains the full set of methods you are used
111
111
 
112
112
  |**django-treebeard** | **django-fast-treenode** |**Features of use**|
113
113
  |---------------------|----------------------|----------------------|
114
- | The `pos` parameter in `add_sibling()` and `move()` methods | The parameter `pos` has the name `position` | • The `position` parameter in `django-fast-treenode` always consists of two parts separated by `-`: the first part determines the insertion method (_first, left, right, last, sorted_), the second — the placement type (_child, sibling_). <br> • Instead of a string format, you can also pass position as an integer indicating the exact position of the node in the list.|
114
+ | The `pos` parameter in `add_sibling()` and `move()` methods | The parameter `pos` has the name `position` | • The `position` parameter in `django-fast-treenode` always consists of two parts separated by <br>`-`: the first part determines the insertion method (_first, left, right, last, sorted_), the second — the placement type (_child, sibling_). <br> • Instead of a string format, you can also pass position as an integer indicating the exact position of the node in the list.|
115
115
  |`get_siblings()`, `get_ancestors()` end ect. | Similar methods have parameters `include_self` and `depth` |• The `include_self` parameter specifies whether to include the node itself in the selection. <br> • The `depth` parameter specifies the depth of the search. |
116
116
  |`move(target, pos)` metiod| The method `move()` has the name `move_to(target, position)` | - |
117
117
 
@@ -0,0 +1,63 @@
1
+ ## Roadmap
2
+
3
+ ### Last Update
4
+ The latest version provides optimized database operations, an improved caching mechanism, and enhanced integration capabilities, making it a **robust and efficient choice** for handling tree structures.
5
+
6
+ **Version 2.1 – Compatibility and Optimization**
7
+ Reducing dependencies and simplifying migration from other libraries.
8
+
9
+ - **Expanding functionality** to simplify migration from other tree packages.
10
+ - Introducing node **filtering and search with AJAX**.
11
+ - **Optimizing query performance** by reducing query complexity and improving indexing strategies.
12
+ - **Reducing database load** from the `TreeNodeAdmin` when working with large trees.
13
+ - Removing `numpy` in favor of lighter alternatives.
14
+ - Removing `Select2` in favor of light custom select2-like implementation.
15
+
16
+
17
+ ### Planned Roadmap for Future Versions
18
+ The **django-fast-treenode** package will continue to evolve from its original concept—combining the benefits of the **Adjacency List** and **Closure Table** models—into a high-performance solution for managing and visualizing hierarchical data in Django projects. The focus is on **speed, usability, and flexibility**.
19
+
20
+ #### Version 2.2 – Performance Enhancements
21
+ Focusing on optimizing query efficiency, reducing database load, and improving API interactions.
22
+
23
+ - **Optimized Query Execution**: Minimize query overhead by restructuring SQL operations, reducing redundant lookups, and introducing batched operations for bulk inserts.
24
+ - **API-First CRUD Implementation**: Introduce full Create, Read, Update, and Delete (CRUD) operations for tree structures, ensuring seamless API-based interaction with hierarchical data.
25
+ - **Efficient Serialization**: Develop a lightweight tree serialization format optimized for API responses, reducing payload size while preserving structural integrity.
26
+ - **Advanced Node Filtering & Search**: Implement AJAX-based filtering and search mechanisms in Django Admin and API endpoints to enhance usability and response time.
27
+
28
+ #### Version 2.3 – Drag-and-Drop and Admin UI Improvements
29
+ Improving the usability and management of hierarchical structures in Django Admin.
30
+
31
+ - **Drag-and-Drop Node Reordering**: Introduce interactive drag-and-drop functionality for reordering nodes within the tree structure directly in Django Admin.
32
+ - **Hierarchical Sorting Strategies**: Enable various sorting methods, including manual ordering, weight-based prioritization, and hybrid approaches that combine automatic and manual sorting.
33
+ - **Admin Panel Enhancements**: Expand Django Admin capabilities for tree structures, including better visualization, inline node editing, and bulk actions.
34
+
35
+ #### Version 3.0 – Third-Generation Cache Management System
36
+ Introducing a more advanced caching system to improve scalability and efficiency.
37
+
38
+ - **Two-Level FIFO/LRU Cache**: Implement a hybrid caching mechanism combining First-In-First-Out (FIFO) and Least Recently Used (LRU) strategies to optimize cache retention for tree nodes.
39
+ - **Multi-Process Cache Synchronization**: Ensure cache consistency across different execution environments (WSGI, Gunicorn, Uvicorn) with a distributed synchronization mechanism.
40
+ - **Background Synchronization**: Introduce delayed closure table updates via Celery or RQ, preventing blocking operations while maintaining data consistency.
41
+
42
+ #### Version 4.0 – Asynchronous Operations
43
+ Refactoring the package to support fully asynchronous operations for non-blocking execution.
44
+
45
+ - **Asynchronous API Execution**: Convert existing synchronous operations to asynchronous, leveraging `async/await` for improved performance.
46
+ - **Async Database Support**: Implement async-friendly database operations compatible with Django’s evolving asynchronous ORM.
47
+ - **Optimized Tree Node Caching**: Shift from caching precomputed query results to caching raw tree nodes, reducing recomputation overhead and improving retrieval speed.
48
+ - **Asynchronous Testing**: Expand the test suite to cover async behavior and the new caching mechanism under concurrent loads.
49
+ - **Documentation Update**: Revise and expand documentation to reflect changes in the asynchronous execution model and best practices for implementation.
50
+
51
+ #### Version 5.0 – Beyond Django ORM
52
+ Decoupling tree structure management from Django’s ORM to increase flexibility and adaptability.
53
+
54
+ - **Multi-Backend Storage Support**: Introduce support for alternative storage engines beyond Django ORM, such as SQLAlchemy, custom PostgreSQL functions, and other database frameworks.
55
+ - **Redis Integration for In-Memory Trees**: Implement an optional Redis-based tree storage system, allowing high-speed in-memory hierarchy operations.
56
+ - **JSON-Based Storage Option**: Enable lightweight embedded tree storage using JSON structures, facilitating easier use in API-driven and microservice architectures.
57
+ - **ORM-Agnostic API Layer**: Design an API-first approach that allows tree structures to function independently from Django models, making the package usable in broader contexts.
58
+
59
+ So, each milestone is designed to improve performance, scalability, and flexibility, ensuring that the package remains relevant for modern web applications, API-driven architectures, and high-performance data processing environments support.
60
+
61
+ Stay tuned for updates!
62
+
63
+ Your wishes, objections, and comments are welcome.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "django-fast-treenode"
7
- version = "2.1.2"
7
+ version = "2.1.3"
8
8
  description = "Application for supporting tree (hierarchical) data structure in Django projects"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Timur Kady", email = "timurkady@yandex.com" }]
@@ -48,3 +48,4 @@ Homepage = "https://github.com/TimurKady/django-fast-treenode"
48
48
  Documentation = "https://django-fast-treenode.readthedocs.io/"
49
49
  Source = "https://github.com/TimurKady/django-fast-treenode"
50
50
  Issues = "https://github.com/TimurKady/django-fast-treenode/issues"
51
+
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = django-fast-treenode
3
- version = 2.1.2
3
+ version = 2.1.3
4
4
  description = Application for supporting tree (hierarchical) data structure in Django projects
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
@@ -1,9 +1,18 @@
1
+ import os
2
+ import django
1
3
  import time
2
4
  import traceback
5
+ from django.db import models
3
6
  from django.test import TestCase, TransactionTestCase
4
- from devapp.models import Entity
7
+ from . import settings
8
+ from .models import TestModel
5
9
 
6
10
 
11
+ if "DJANGO_SETTINGS_MODULE" not in os.environ:
12
+ os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings"
13
+
14
+ django.setup()
15
+
7
16
  class BasicOperationsTest(TestCase):
8
17
  """
9
18
  Tests basic operations with nodes: insertion (using three methods), deletion,
@@ -16,7 +25,7 @@ class BasicOperationsTest(TestCase):
16
25
 
17
26
  # 1. Insertion via node.save()
18
27
  try:
19
- node1 = Entity(name='Node via save()')
28
+ node1 = TestModel(name='Node via save()')
20
29
  node1.save()
21
30
  if not node1.pk:
22
31
  errors.append("Insertion via save() did not assign a PK.")
@@ -25,7 +34,7 @@ class BasicOperationsTest(TestCase):
25
34
 
26
35
  # 2. Insertion via objects.create()
27
36
  try:
28
- node2 = Entity.objects.create(name='Node via create()')
37
+ node2 = TestModel.objects.create(name='Node via create()')
29
38
  if not node1.pk:
30
39
  errors.append(
31
40
  "Insertion via objects.create() did not assign a PK.")
@@ -36,11 +45,11 @@ class BasicOperationsTest(TestCase):
36
45
  # 3. Alternative method (insert_at or get_or_create)
37
46
  try:
38
47
  # For demonstration, use node1 as the parent if the insert_at method exists
39
- if hasattr(Entity, 'insert_at'):
40
- node3 = Entity(name='Node via insert_at()')
48
+ if hasattr(TestModel, 'insert_at'):
49
+ node3 = TestModel(name='Node via insert_at()')
41
50
  node3.insert_at(node1)
42
- elif hasattr(Entity.objects, 'get_or_create'):
43
- node3, created = Entity.objects.get_or_create(
51
+ elif hasattr(TestModel.objects, 'get_or_create'):
52
+ node3, created = TestModel.objects.get_or_create(
44
53
  name='Node via get_or_create()', defaults={'tn_parent': node1})
45
54
  else:
46
55
  errors.append(
@@ -52,8 +61,8 @@ class BasicOperationsTest(TestCase):
52
61
  # Checking access methods (e.g. get_children)
53
62
  try:
54
63
  # Create a small tree
55
- root = Entity.objects.create(name='Root')
56
- child = Entity.objects.create(name='Child', tn_parent=root)
64
+ root = TestModel.objects.create(name='Root')
65
+ child = TestModel.objects.create(name='Child', tn_parent=root)
57
66
  # If the model defines a get_children() method, check its functionality
58
67
  if hasattr(root, 'get_children'):
59
68
  children = root.get_children()
@@ -62,7 +71,7 @@ class BasicOperationsTest(TestCase):
62
71
  "Method get_children() did not return the added child.")
63
72
  else:
64
73
  # If the method does not exist, perform a simple filter by parent
65
- children = Entity.objects.filter(tn_parent=root)
74
+ children = TestModel.objects.filter(tn_parent=root)
66
75
  if child not in children:
67
76
  errors.append(
68
77
  "Filter by parent did not return the added child.")
@@ -99,25 +108,24 @@ class DeepTreePerformanceTest(TransactionTestCase):
99
108
  try:
100
109
  # Insertion of the root node
101
110
  start = time.time()
102
- root = Entity.objects.create(name="Deep Root")
111
+ root = TestModel.objects.create(name="Deep Root")
103
112
  end = time.time()
104
113
  timings[0] = end - start
105
114
  nodes.append(root)
106
115
 
107
116
  current_parent = root
108
117
 
109
- for level in range(1, 201):
118
+ for level in range(1, 101):
110
119
  start = time.time()
111
- new_node = Entity.objects.create(
120
+ new_node = TestModel.objects.create(
112
121
  name=f"Node Level {level}", tn_parent=current_parent)
113
122
  end = time.time()
114
123
  timings[level] = end - start
115
124
  nodes.append(new_node)
116
125
  current_parent = new_node # the next node will be a child of the one just created
117
126
 
118
- # Generate report for levels: 0, 1, 10, 20, 30, 40, 50, 70, 80, 90, 100, 150, 200
119
- levels_to_report = [0, 1, 10, 20, 30,
120
- 40, 50, 70, 80, 90, 100, 150, 200]
127
+ # Generate report for levels: 0, 1, 10, 20, 30, 40, 50, 70, 80, 90, 100
128
+ levels_to_report = [0, 1, 10, 20, 30,40, 50, 70, 80, 90, 100]
121
129
  # to avoid division by zero
122
130
  root_time = timings[0] if timings[0] > 0 else 1e-6
123
131
  print("Deep tree - node insertion times:")
@@ -154,11 +162,11 @@ class WideTreePerformanceTest(TransactionTestCase):
154
162
  # For 100 trees
155
163
  for root_index in range(1, 101):
156
164
  tree_start = time.time()
157
- root = Entity.objects.create(name=f"Wide Root {root_index}")
165
+ root = TestModel.objects.create(name=f"Wide Root {root_index}")
158
166
  current_parent = root
159
167
  # Create 5 levels of nesting for each root
160
168
  for level in range(1, 6):
161
- new_node = Entity.objects.create(
169
+ new_node = TestModel.objects.create(
162
170
  name=f"Node {root_index}-{level}", tn_parent=current_parent)
163
171
  current_parent = new_node
164
172
  tree_end = time.time()
@@ -190,29 +198,29 @@ class MassInsertionTest(TestCase):
190
198
  for i in range(50, 76):
191
199
  # 1. Insertion via save()
192
200
  try:
193
- node = Entity(name=f"Mass Node {i} via save()")
201
+ node = TestModel(name=f"Mass Node {i} via save()")
194
202
  node.save()
195
203
  except Exception as e:
196
204
  errors.append(f"Error inserting node {i} via save(): {e}")
197
205
 
198
206
  # 2. Insertion via objects.create()
199
207
  try:
200
- Entity.objects.create(name=f"Mass Node {i} via create()")
208
+ TestModel.objects.create(name=f"Mass Node {i} via create()")
201
209
  except Exception as e:
202
210
  errors.append(f"Error inserting node {i} via create(): {e}")
203
211
 
204
212
  # 3. Alternative method (insert_at or get_or_create)
205
213
  try:
206
214
  # Take the first node in the database or create a parent
207
- parent = Entity.objects.first()
215
+ parent = TestModel.objects.first()
208
216
  if not parent:
209
- parent = Entity.objects.create(name="Default Parent")
217
+ parent = TestModel.objects.create(name="Default Parent")
210
218
 
211
- if hasattr(Entity, 'insert_at'):
212
- node = Entity(name=f"Mass Node {i} via insert_at()")
219
+ if hasattr(TestModel, 'insert_at'):
220
+ node = TestModel(name=f"Mass Node {i} via insert_at()")
213
221
  node.insert_at(parent)
214
- elif hasattr(Entity.objects, 'get_or_create'):
215
- Entity.objects.get_or_create(
222
+ elif hasattr(TestModel.objects, 'get_or_create'):
223
+ TestModel.objects.get_or_create(
216
224
  name=f"Mass Node {i} via get_or_create()", defaults={'tn_parent': parent})
217
225
  else:
218
226
  errors.append(
@@ -246,20 +254,20 @@ class NodeDeletionTest(TestCase):
246
254
  errors = []
247
255
  print("\n=== NodeDeletionTest ===")
248
256
  try:
249
- parent = Entity.objects.create(name="Deletion Parent")
250
- child1 = Entity.objects.create(name="Child 1", tn_parent=parent)
251
- child2 = Entity.objects.create(name="Child 2", tn_parent=parent)
257
+ parent = TestModel.objects.create(name="Deletion Parent")
258
+ child1 = TestModel.objects.create(name="Child 1", tn_parent=parent)
259
+ child2 = TestModel.objects.create(name="Child 2", tn_parent=parent)
252
260
 
253
261
  # Delete child1
254
262
  child1.delete()
255
- remaining_children = Entity.objects.filter(tn_parent=parent)
263
+ remaining_children = TestModel.objects.filter(tn_parent=parent)
256
264
  if child1 in remaining_children:
257
265
  errors.append(
258
266
  "The deleted child1 is still present among the parent's children.")
259
267
 
260
268
  # Delete the parent and check cascading deletion
261
269
  parent.delete()
262
- if Entity.objects.filter(pk=child2.pk).exists():
270
+ if TestModel.objects.filter(pk=child2.pk).exists():
263
271
  errors.append(
264
272
  "Child2 was not deleted after the parent was deleted (cascading deletion was expected).")
265
273
  except Exception as e:
@@ -288,17 +296,17 @@ class NodeMovingTest(TestCase):
288
296
  errors = []
289
297
  print("\n=== NodeMovingTest ===")
290
298
  try:
291
- parent1 = Entity.objects.create(name="Original Parent")
292
- parent2 = Entity.objects.create(name="New Parent")
293
- child = Entity.objects.create(
299
+ parent1 = TestModel.objects.create(name="Original Parent")
300
+ parent2 = TestModel.objects.create(name="New Parent")
301
+ child = TestModel.objects.create(
294
302
  name="Movable Child", tn_parent=parent1)
295
303
 
296
304
  # Moving the node
297
305
  child.set_parent(parent2)
298
306
  child.save()
299
307
 
300
- children1 = Entity.objects.filter(tn_parent=parent1)
301
- children2 = Entity.objects.filter(tn_parent=parent2)
308
+ children1 = TestModel.objects.filter(tn_parent=parent1)
309
+ children2 = TestModel.objects.filter(tn_parent=parent2)
302
310
  if child in children1:
303
311
  errors.append(
304
312
  "The node is still present in the original parent's children after moving.")
@@ -330,10 +338,10 @@ class NodeUpdateTest(TestCase):
330
338
  errors = []
331
339
  print("\n=== NodeUpdateTest ===")
332
340
  try:
333
- node = Entity.objects.create(name="Original Name")
341
+ node = TestModel.objects.create(name="Original Name")
334
342
  node.name = "Updated Name"
335
343
  node.save()
336
- updated_node = Entity.objects.get(pk=node.pk)
344
+ updated_node = TestModel.objects.get(pk=node.pk)
337
345
  if updated_node.name != "Updated Name":
338
346
  errors.append("The node's name was not updated correctly.")
339
347
  except Exception as e:
@@ -362,12 +370,12 @@ class DataIntegrityTest(TestCase):
362
370
  errors = []
363
371
  print("\n=== DataIntegrityTest ===")
364
372
  try:
365
- root = Entity.objects.create(name="Integrity Root")
366
- child1 = Entity.objects.create(
373
+ root = TestModel.objects.create(name="Integrity Root")
374
+ child1 = TestModel.objects.create(
367
375
  name="Integrity Child 1", tn_parent=root)
368
- child2 = Entity.objects.create(
376
+ child2 = TestModel.objects.create(
369
377
  name="Integrity Child 2", tn_parent=root)
370
- grandchild = Entity.objects.create(
378
+ grandchild = TestModel.objects.create(
371
379
  name="Integrity Grandchild", tn_parent=child1)
372
380
 
373
381
  # Move child2 under child1
@@ -381,7 +389,7 @@ class DataIntegrityTest(TestCase):
381
389
  grandchild.delete()
382
390
 
383
391
  # Verify relationships
384
- root_children = Entity.objects.filter(tn_parent=root)
392
+ root_children = TestModel.objects.filter(tn_parent=root)
385
393
  if child1 not in root_children:
386
394
  errors.append(
387
395
  "Child1 not found among the root node's children.")
@@ -389,12 +397,12 @@ class DataIntegrityTest(TestCase):
389
397
  errors.append(
390
398
  "Child2 is present among the root node's children after moving.")
391
399
 
392
- child1_children = Entity.objects.filter(tn_parent=child1)
400
+ child1_children = TestModel.objects.filter(tn_parent=child1)
393
401
  if child2 not in child1_children:
394
402
  errors.append(
395
403
  "Child2 not found among child1's children after moving.")
396
404
 
397
- if Entity.objects.filter(name="Integrity Grandchild").exists():
405
+ if TestModel.objects.filter(name="Integrity Grandchild").exists():
398
406
  errors.append("Grandchild still exists after deletion.")
399
407
  except Exception as e:
400
408
  errors.append("Exception in DataIntegrityTest: " + str(e))
@@ -421,14 +429,14 @@ class LargeVolumeTest(TransactionTestCase):
421
429
  errors = []
422
430
  print("\n=== LargeVolumeTest ===")
423
431
  try:
424
- initial_count = Entity.objects.count()
432
+ initial_count = TestModel.objects.count()
425
433
  for i in range(1000):
426
434
  try:
427
- Entity.objects.create(name=f"Large Node {i}")
435
+ TestModel.objects.create(name=f"Large Node {i}")
428
436
  except Exception as e:
429
437
  errors.append(
430
438
  f"Error inserting node {i} in large data volume: {e}")
431
- final_count = Entity.objects.count()
439
+ final_count = TestModel.objects.count()
432
440
  if final_count - initial_count != 1000:
433
441
  errors.append(
434
442
  f"Expected 1000 new nodes, but got {final_count - initial_count}.")
@@ -459,7 +467,7 @@ class InvalidDataTest(TestCase):
459
467
  try:
460
468
  try:
461
469
  # Attempt to create a node with invalid data
462
- Entity.objects.create(name=None)
470
+ TestModel.objects.create(name=None)
463
471
  errors.append(
464
472
  "Creating a node with None for name did not raise an exception as expected.")
465
473
  except Exception:
@@ -468,7 +476,7 @@ class InvalidDataTest(TestCase):
468
476
 
469
477
  # Additionally, one can test creating a node with an empty string if that is not allowed
470
478
  try:
471
- node = Entity(name="")
479
+ node = TestModel(name="")
472
480
  node.save()
473
481
  # If an empty string is allowed, then no error should be recorded
474
482
  except Exception as e:
@@ -12,7 +12,7 @@ Features:
12
12
  - Automatic cache eviction when memory limits are exceeded.
13
13
  - Decorator `@cached_method` for caching method results.
14
14
 
15
- Version: 2.0.0
15
+ Version: 2.1.0
16
16
  Author: Timur Kady
17
17
  Email: timurkady@yandex.com
18
18
  """
@@ -23,7 +23,7 @@ class TreeNodeNodeMixin(models.Model):
23
23
  abstract = True
24
24
 
25
25
  @cached_method
26
- def get_breadcrumbs(self, attr='pk'):
26
+ def get_breadcrumbs(self, attr='id'):
27
27
  """Optimized breadcrumbs retrieval with direct cache check."""
28
28
  try:
29
29
  self._meta.get_field(attr)
@@ -10,4 +10,4 @@ Email: timurkady@yandex.com
10
10
  """
11
11
 
12
12
 
13
- __version__ = '2.1.2'
13
+ __version__ = '2.1.3'
@@ -1,30 +0,0 @@
1
- # Django Fast TreeNode
2
-
3
- Welcome to the **django-fast-treenode** documentation!
4
-
5
- ## Key Features
6
- **django-fast-treenode** is a high-performance solution for managing tree structures in Django.
7
-
8
- - **Fast processing** even with deep trees
9
- - **Intuitive API** for seamless integration
10
- - **Django Admin support** for easy management
11
- - **Optimized caching** for enhanced performance
12
-
13
- ## Contents
14
- - [About the Project](about.md)
15
- - [Installation, Configuration, and Fine-Tuning](installation.md)
16
- - [Model Inheritance and Extensions](models.md)
17
- - [Working with Admin Classes](admin.md)
18
- - [API Reference](api.md)
19
- - [Import & Export](import_export.md)
20
- - [Caching and Cache Management](cache.md)
21
- - [Migration and Upgrade Guide](migration.md)
22
- - [Roadmap](roadmap.md)
23
-
24
- ## Links
25
- - [Issues](https://github.com/TimurKady/django-fast-treenode/issues)
26
- - [Pull Requests](https://github.com/TimurKady/django-fast-treenode/pulls)
27
- - [Discussions](https://github.com/TimurKady/django-fast-treenode/discussions)
28
-
29
- ## License
30
- Released under the [MIT License](https://github.com/TimurKady/django-fast-treenode/blob/main/LICENSE).
@@ -1,45 +0,0 @@
1
- ## Roadmap
2
- The latest version provides optimized database operations, an improved caching mechanism, and enhanced integration capabilities, making it a **robust and efficient choice** for handling tree structures.
3
-
4
- But the **django-fast-treenode** package will continue to evolve from its original concept—combining the benefits of the **Adjacency List** and **Closure Table** models—into a high-performance solution for managing and visualizing hierarchical data in Django projects. The focus is on **speed, usability, and flexibility**.
5
-
6
- In future versions, I plan to implement:
7
-
8
- ### **Version 2.1 – Compatibility and Optimization**
9
- Reducing dependencies and simplifying migration from other libraries.
10
- - **Expanding functionality** to simplify migration from other tree packages.
11
- - Introducing node **filtering and search with AJAX**.
12
- - **Optimizing query performance** by reducing query complexity and improving indexing strategies.
13
- - **Reducing database load** from the `TreeNodeAdmin` when working with large trees.
14
- - Removing `numpy` in favor of lighter alternatives.
15
-
16
- ### **Version 2.2 – Caching Improvements**
17
- Speeding up tree operations and reducing database load.
18
- - **Implementing a more efficient caching algorithm** to optimize performance and reduce recomputation.
19
- - **Refining cache expiration strategies** for better memory management.
20
-
21
- ### **Version 2.3 – Performance Enhancements**
22
- - **Reducing query overhead** and optimizing bulk operations for large datasets.
23
- - **Django REST Framework (DRF)**: Adding initial integration for efficient tree-based API handling.
24
- - **Serialization**: Developing lightweight tree serialization for API-first projects.
25
- - **Enhancing node filtering and search** with AJAX-driven interfaces.
26
-
27
- ### **Version 2.4 – Drag-and-Drop and Admin UI Improvements**
28
- - **Drag-and-Drop**: Adding drag-and-drop support for node sorting in Django admin.
29
- - **Advanced Sorting**: Adding multiple sorting strategies for tree nodes, including manual ordering and hierarchical priority rules.
30
- - **Admin Panel Enhancements**: Expanding the functionality of the Django admin interface for managing hierarchical data more efficiently.
31
-
32
- ### **Version 3.0 – Asynchronous operations and Fourth-Generation Cache Management System**
33
- - **Asynchronous operations** support, ensuring efficient working and data processing in non-blocking environments.
34
- - **Shifting from caching computed results to directly caching tree nodes**, reducing recomputation overhead and improving cache efficiency.
35
- - **Reworking Adjacency List and Closure Table managers** for a new caching system.
36
- - **Enhancing cache consistency** across multiple processes (WSGI, Gunicorn, Uvicorn) with a global synchronization mechanism.
37
-
38
- ### **Version 4.0 – Moving Beyond Django ORM**
39
- Enabling tree structures to function without a strict dependency on Django ORM while maintaining compatibility.
40
- - **Introducing support for various storage backends**, allowing greater flexibility.
41
- - **Adding compatibility with Redis for in-memory tree storage** and JSON-based trees for lightweight embedded storage.
42
- - **Expanding usage in API-first projects**, enabling tree structures to work independently from Django models.
43
-
44
- Stay tuned for updates!
45
- Your wishes, objections, and comments are welcome.