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