0
# Tree Operations
1
2
Core methods for creating, reading, updating, and deleting tree nodes. These operations work consistently across all tree implementations (AL_Node, MP_Node, NS_Node) while being optimized for each algorithm's characteristics.
3
4
## Capabilities
5
6
### Node Creation
7
8
Methods for adding new nodes to the tree structure.
9
10
#### Adding Root Nodes
11
12
```python { .api }
13
@classmethod
14
def add_root(**kwargs):
15
"""
16
Add a root node to the tree.
17
18
The new root node becomes the rightmost root node.
19
20
Parameters:
21
**kwargs: Field values for the new node
22
23
Returns:
24
Node: The created and saved node instance
25
26
Raises:
27
NodeAlreadySaved: If 'instance' parameter is already saved
28
"""
29
```
30
31
Usage example:
32
```python
33
# Create root nodes
34
electronics = Category.add_root(name='Electronics', slug='electronics')
35
books = Category.add_root(name='Books', slug='books')
36
```
37
38
#### Adding Child Nodes
39
40
```python { .api }
41
def add_child(**kwargs):
42
"""
43
Add a child node to this node.
44
45
Child is added as the rightmost child.
46
47
Parameters:
48
**kwargs: Field values for the new child node
49
50
Returns:
51
Node: The created and saved child node
52
53
Raises:
54
NodeAlreadySaved: If 'instance' parameter is already saved
55
"""
56
```
57
58
Usage example:
59
```python
60
# Add children to existing nodes
61
phones = electronics.add_child(name='Phones', slug='phones')
62
laptops = electronics.add_child(name='Laptops', slug='laptops')
63
```
64
65
#### Adding Sibling Nodes
66
67
```python { .api }
68
def add_sibling(pos=None, **kwargs):
69
"""
70
Add a sibling node to this node.
71
72
Parameters:
73
pos (str, optional): Position relative to current node
74
- 'first-sibling': Leftmost sibling position
75
- 'left': Before current node
76
- 'right': After current node
77
- 'last-sibling': Rightmost sibling position
78
- 'sorted-sibling': Position based on node_order_by
79
**kwargs: Field values for the new sibling node
80
81
Returns:
82
Node: The created and saved sibling node
83
84
Raises:
85
InvalidPosition: If pos value is invalid
86
NodeAlreadySaved: If 'instance' parameter is already saved
87
"""
88
```
89
90
Usage example:
91
```python
92
# Add sibling nodes with positioning
93
tablets = phones.add_sibling('right', name='Tablets', slug='tablets')
94
accessories = laptops.add_sibling('left', name='Accessories', slug='accessories')
95
```
96
97
### Bulk Operations
98
99
Methods for loading and exporting tree data structures.
100
101
#### Loading Bulk Data
102
103
```python { .api }
104
@classmethod
105
def load_bulk(bulk_data, parent=None, keep_ids=False):
106
"""
107
Load a tree structure from nested data.
108
109
Efficiently creates multiple nodes from hierarchical data structure.
110
111
Parameters:
112
bulk_data (list): List of dictionaries representing nodes
113
Each dict can contain 'children' key with nested nodes
114
parent (Node, optional): Parent node for the loaded data
115
keep_ids (bool): Whether to preserve 'id' values from data
116
117
Returns:
118
list: List of created root nodes
119
120
Example data format:
121
[
122
{'name': 'Root1', 'children': [
123
{'name': 'Child1'},
124
{'name': 'Child2', 'children': [
125
{'name': 'Grandchild1'}
126
]}
127
]},
128
{'name': 'Root2'}
129
]
130
"""
131
```
132
133
Usage example:
134
```python
135
data = [
136
{
137
'name': 'Science',
138
'children': [
139
{'name': 'Physics'},
140
{'name': 'Chemistry', 'children': [
141
{'name': 'Organic'},
142
{'name': 'Inorganic'}
143
]}
144
]
145
},
146
{'name': 'Fiction'}
147
]
148
Category.load_bulk(data, parent=books)
149
```
150
151
#### Exporting Bulk Data
152
153
```python { .api }
154
@classmethod
155
def dump_bulk(parent=None, keep_ids=True):
156
"""
157
Export tree structure to nested data format.
158
159
Parameters:
160
parent (Node, optional): Root node to export from (default: all roots)
161
keep_ids (bool): Whether to include node IDs in export
162
163
Returns:
164
list: Nested list/dict structure representing the tree
165
"""
166
```
167
168
Usage example:
169
```python
170
# Export entire tree
171
tree_data = Category.dump_bulk()
172
173
# Export specific subtree
174
subtree_data = Category.dump_bulk(parent=electronics, keep_ids=False)
175
```
176
177
### Node Movement
178
179
Methods for moving nodes within the tree structure.
180
181
```python { .api }
182
def move(target, pos=None):
183
"""
184
Move this node to a new position in the tree.
185
186
Parameters:
187
target (Node): Reference node for the move operation
188
pos (str, optional): Position relative to target node
189
- 'first-child': Leftmost child of target
190
- 'last-child': Rightmost child of target
191
- 'sorted-child': Child position based on node_order_by
192
- 'first-sibling': Leftmost sibling of target
193
- 'left': Before target node
194
- 'right': After target node
195
- 'last-sibling': Rightmost sibling of target
196
- 'sorted-sibling': Sibling position based on node_order_by
197
198
Raises:
199
InvalidPosition: If pos value is invalid
200
InvalidMoveToDescendant: If trying to move to descendant
201
"""
202
```
203
204
Usage example:
205
```python
206
# Move phone to be child of accessories
207
phones.move(accessories, 'last-child')
208
209
# Move node to specific sibling position
210
tablets.move(laptops, 'left')
211
212
# Move with automatic positioning (requires node_order_by)
213
smartphones.move(electronics, 'sorted-child')
214
```
215
216
### Node Deletion
217
218
Methods for removing nodes and their descendants.
219
220
```python { .api }
221
def delete(self):
222
"""
223
Delete this node and all its descendants.
224
225
Automatically handles tree structure updates and descendant removal.
226
For large subtrees, this operation may be expensive.
227
228
Returns:
229
tuple: (number_deleted, {model_label: count})
230
"""
231
```
232
233
Usage example:
234
```python
235
# Delete node and all descendants
236
phones.delete() # Also deletes smartphones, tablets, etc.
237
238
# Delete multiple nodes efficiently (using queryset)
239
Category.objects.filter(name__startswith='Old').delete()
240
```
241
242
### Root Node Management
243
244
Methods for working with root-level nodes.
245
246
```python { .api }
247
@classmethod
248
def get_root_nodes(cls):
249
"""
250
Get queryset of all root nodes.
251
252
Returns:
253
QuerySet: All root nodes in tree order
254
"""
255
256
@classmethod
257
def get_first_root_node(cls):
258
"""
259
Get the first root node.
260
261
Returns:
262
Node or None: First root node or None if no roots exist
263
"""
264
265
@classmethod
266
def get_last_root_node(cls):
267
"""
268
Get the last root node.
269
270
Returns:
271
Node or None: Last root node or None if no roots exist
272
"""
273
```
274
275
Usage example:
276
```python
277
# Get all root categories
278
roots = Category.get_root_nodes()
279
280
# Get specific root nodes
281
first_root = Category.get_first_root_node()
282
last_root = Category.get_last_root_node()
283
284
# Iterate through roots
285
for root in Category.get_root_nodes():
286
print(f"Root: {root.name}")
287
```
288
289
## Position Constants
290
291
### Valid Positions for add_sibling()
292
293
```python { .api }
294
_valid_pos_for_add_sibling = [
295
'first-sibling', # New leftmost sibling
296
'left', # Before current node
297
'right', # After current node
298
'last-sibling', # New rightmost sibling
299
'sorted-sibling' # Position by node_order_by
300
]
301
```
302
303
### Valid Positions for move()
304
305
```python { .api }
306
_valid_pos_for_move = [
307
'first-child', # New leftmost child
308
'last-child', # New rightmost child
309
'sorted-child', # Child position by node_order_by
310
'first-sibling', # New leftmost sibling
311
'left', # Before target node
312
'right', # After target node
313
'last-sibling', # New rightmost sibling
314
'sorted-sibling' # Sibling position by node_order_by
315
]
316
```
317
318
## Advanced Usage Patterns
319
320
### Ordered Tree Operations
321
322
When `node_order_by` is configured, use sorted positions for automatic ordering:
323
324
```python
325
class Category(MP_Node):
326
name = models.CharField(max_length=100)
327
priority = models.IntegerField(default=0)
328
329
node_order_by = ['priority', 'name']
330
331
# Nodes will be automatically positioned by priority, then name
332
cat1 = Category.add_root(name='High Priority', priority=1)
333
cat2 = Category.add_root(name='Low Priority', priority=10) # Auto-positioned after cat1
334
335
child = cat1.add_child(name='Child', priority=5)
336
child.move(cat2, 'sorted-child') # Positioned by priority within cat2's children
337
```
338
339
### Bulk Loading with Foreign Keys
340
341
Handle foreign key relationships during bulk loading:
342
343
```python
344
# Data with foreign key references
345
data = [
346
{
347
'name': 'Books',
348
'category_type_id': 1, # References CategoryType model
349
'children': [
350
{'name': 'Fiction', 'category_type_id': 2},
351
{'name': 'Non-Fiction', 'category_type_id': 2}
352
]
353
}
354
]
355
356
# Foreign keys are automatically handled
357
Category.load_bulk(data)
358
```
359
360
### Transaction Safety
361
362
Large tree operations should use database transactions:
363
364
```python
365
from django.db import transaction
366
367
with transaction.atomic():
368
# Multiple tree operations as single transaction
369
root = Category.add_root(name='New Section')
370
371
for item in bulk_items:
372
root.add_child(**item)
373
374
# Move existing subtree
375
old_section.move(root, 'first-child')
376
```
377
378
### Performance Considerations
379
380
- **Bulk Loading**: Use `load_bulk()` instead of multiple `add_child()` calls
381
- **Large Moves**: Moving large subtrees can be expensive, especially in NS_Node
382
- **Deletions**: Deleting nodes with many descendants requires multiple database operations
383
- **Transactions**: Wrap multiple operations in transactions for consistency and performance
384
385
### Error Handling
386
387
```python
388
from treebeard.exceptions import InvalidPosition, InvalidMoveToDescendant
389
390
try:
391
# Attempt to move node
392
child.move(grandchild, 'first-child') # Invalid: moving to descendant
393
except InvalidMoveToDescendant:
394
print("Cannot move node to its own descendant")
395
396
try:
397
# Invalid position
398
node.add_sibling('invalid-position', name='Test')
399
except InvalidPosition:
400
print("Invalid position specified")
401
```