Efficient tree implementations for Django models providing three different tree algorithms with unified API
—
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.
Methods for adding new nodes to the tree structure.
@classmethod
def add_root(**kwargs):
"""
Add a root node to the tree.
The new root node becomes the rightmost root node.
Parameters:
**kwargs: Field values for the new node
Returns:
Node: The created and saved node instance
Raises:
NodeAlreadySaved: If 'instance' parameter is already saved
"""Usage example:
# Create root nodes
electronics = Category.add_root(name='Electronics', slug='electronics')
books = Category.add_root(name='Books', slug='books')def add_child(**kwargs):
"""
Add a child node to this node.
Child is added as the rightmost child.
Parameters:
**kwargs: Field values for the new child node
Returns:
Node: The created and saved child node
Raises:
NodeAlreadySaved: If 'instance' parameter is already saved
"""Usage example:
# Add children to existing nodes
phones = electronics.add_child(name='Phones', slug='phones')
laptops = electronics.add_child(name='Laptops', slug='laptops')def add_sibling(pos=None, **kwargs):
"""
Add a sibling node to this node.
Parameters:
pos (str, optional): Position relative to current node
- 'first-sibling': Leftmost sibling position
- 'left': Before current node
- 'right': After current node
- 'last-sibling': Rightmost sibling position
- 'sorted-sibling': Position based on node_order_by
**kwargs: Field values for the new sibling node
Returns:
Node: The created and saved sibling node
Raises:
InvalidPosition: If pos value is invalid
NodeAlreadySaved: If 'instance' parameter is already saved
"""Usage example:
# Add sibling nodes with positioning
tablets = phones.add_sibling('right', name='Tablets', slug='tablets')
accessories = laptops.add_sibling('left', name='Accessories', slug='accessories')Methods for loading and exporting tree data structures.
@classmethod
def load_bulk(bulk_data, parent=None, keep_ids=False):
"""
Load a tree structure from nested data.
Efficiently creates multiple nodes from hierarchical data structure.
Parameters:
bulk_data (list): List of dictionaries representing nodes
Each dict can contain 'children' key with nested nodes
parent (Node, optional): Parent node for the loaded data
keep_ids (bool): Whether to preserve 'id' values from data
Returns:
list: List of created root nodes
Example data format:
[
{'name': 'Root1', 'children': [
{'name': 'Child1'},
{'name': 'Child2', 'children': [
{'name': 'Grandchild1'}
]}
]},
{'name': 'Root2'}
]
"""Usage example:
data = [
{
'name': 'Science',
'children': [
{'name': 'Physics'},
{'name': 'Chemistry', 'children': [
{'name': 'Organic'},
{'name': 'Inorganic'}
]}
]
},
{'name': 'Fiction'}
]
Category.load_bulk(data, parent=books)@classmethod
def dump_bulk(parent=None, keep_ids=True):
"""
Export tree structure to nested data format.
Parameters:
parent (Node, optional): Root node to export from (default: all roots)
keep_ids (bool): Whether to include node IDs in export
Returns:
list: Nested list/dict structure representing the tree
"""Usage example:
# Export entire tree
tree_data = Category.dump_bulk()
# Export specific subtree
subtree_data = Category.dump_bulk(parent=electronics, keep_ids=False)Methods for moving nodes within the tree structure.
def move(target, pos=None):
"""
Move this node to a new position in the tree.
Parameters:
target (Node): Reference node for the move operation
pos (str, optional): Position relative to target node
- 'first-child': Leftmost child of target
- 'last-child': Rightmost child of target
- 'sorted-child': Child position based on node_order_by
- 'first-sibling': Leftmost sibling of target
- 'left': Before target node
- 'right': After target node
- 'last-sibling': Rightmost sibling of target
- 'sorted-sibling': Sibling position based on node_order_by
Raises:
InvalidPosition: If pos value is invalid
InvalidMoveToDescendant: If trying to move to descendant
"""Usage example:
# Move phone to be child of accessories
phones.move(accessories, 'last-child')
# Move node to specific sibling position
tablets.move(laptops, 'left')
# Move with automatic positioning (requires node_order_by)
smartphones.move(electronics, 'sorted-child')Methods for removing nodes and their descendants.
def delete(self):
"""
Delete this node and all its descendants.
Automatically handles tree structure updates and descendant removal.
For large subtrees, this operation may be expensive.
Returns:
tuple: (number_deleted, {model_label: count})
"""Usage example:
# Delete node and all descendants
phones.delete() # Also deletes smartphones, tablets, etc.
# Delete multiple nodes efficiently (using queryset)
Category.objects.filter(name__startswith='Old').delete()Methods for working with root-level nodes.
@classmethod
def get_root_nodes(cls):
"""
Get queryset of all root nodes.
Returns:
QuerySet: All root nodes in tree order
"""
@classmethod
def get_first_root_node(cls):
"""
Get the first root node.
Returns:
Node or None: First root node or None if no roots exist
"""
@classmethod
def get_last_root_node(cls):
"""
Get the last root node.
Returns:
Node or None: Last root node or None if no roots exist
"""Usage example:
# Get all root categories
roots = Category.get_root_nodes()
# Get specific root nodes
first_root = Category.get_first_root_node()
last_root = Category.get_last_root_node()
# Iterate through roots
for root in Category.get_root_nodes():
print(f"Root: {root.name}")_valid_pos_for_add_sibling = [
'first-sibling', # New leftmost sibling
'left', # Before current node
'right', # After current node
'last-sibling', # New rightmost sibling
'sorted-sibling' # Position by node_order_by
]_valid_pos_for_move = [
'first-child', # New leftmost child
'last-child', # New rightmost child
'sorted-child', # Child position by node_order_by
'first-sibling', # New leftmost sibling
'left', # Before target node
'right', # After target node
'last-sibling', # New rightmost sibling
'sorted-sibling' # Sibling position by node_order_by
]When node_order_by is configured, use sorted positions for automatic ordering:
class Category(MP_Node):
name = models.CharField(max_length=100)
priority = models.IntegerField(default=0)
node_order_by = ['priority', 'name']
# Nodes will be automatically positioned by priority, then name
cat1 = Category.add_root(name='High Priority', priority=1)
cat2 = Category.add_root(name='Low Priority', priority=10) # Auto-positioned after cat1
child = cat1.add_child(name='Child', priority=5)
child.move(cat2, 'sorted-child') # Positioned by priority within cat2's childrenHandle foreign key relationships during bulk loading:
# Data with foreign key references
data = [
{
'name': 'Books',
'category_type_id': 1, # References CategoryType model
'children': [
{'name': 'Fiction', 'category_type_id': 2},
{'name': 'Non-Fiction', 'category_type_id': 2}
]
}
]
# Foreign keys are automatically handled
Category.load_bulk(data)Large tree operations should use database transactions:
from django.db import transaction
with transaction.atomic():
# Multiple tree operations as single transaction
root = Category.add_root(name='New Section')
for item in bulk_items:
root.add_child(**item)
# Move existing subtree
old_section.move(root, 'first-child')load_bulk() instead of multiple add_child() callsfrom treebeard.exceptions import InvalidPosition, InvalidMoveToDescendant
try:
# Attempt to move node
child.move(grandchild, 'first-child') # Invalid: moving to descendant
except InvalidMoveToDescendant:
print("Cannot move node to its own descendant")
try:
# Invalid position
node.add_sibling('invalid-position', name='Test')
except InvalidPosition:
print("Invalid position specified")Install with Tessl CLI
npx tessl i tessl/pypi-django-treebeard