Efficient tree implementations for Django models providing three different tree algorithms with unified API
—
Django Treebeard provides three different tree implementations, each optimized for specific use cases and performance characteristics. All implementations inherit from the abstract Node base class, providing a unified API while using different database storage strategies.
The abstract base class that defines the common interface for all tree implementations.
class Node(models.Model):
"""
Abstract base class for all tree implementations.
Provides common interface methods but no actual storage fields.
Subclasses must implement specific tree algorithm fields.
"""
class Meta:
abstract = True
# Database connection attribute
_db_connection = NoneTraditional parent-child relationship model, storing direct parent references for each node.
class AL_Node(Node):
"""
Adjacency List tree implementation.
Stores parent-child relationships using foreign keys.
Best for small trees with frequent write operations.
"""
# Required fields (must be defined in your model):
# parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE)
# sib_order = models.PositiveIntegerField()
# Configuration
node_order_by = None # List of field names for ordering
objects = AL_NodeManager()
class Meta:
abstract = TrueWhen using AL_Node, you must define these fields in your model:
class YourModel(AL_Node):
parent = models.ForeignKey(
'self',
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='children'
)
sib_order = models.PositiveIntegerField()
# Your additional fields here
name = models.CharField(max_length=255)Custom manager that provides ordered querysets based on sibling order.
class AL_NodeManager(models.Manager):
"""
Custom manager for Adjacency List trees.
Automatically orders nodes by sib_order field.
"""
def get_queryset(self):
"""
Returns queryset ordered by sib_order.
Returns:
QuerySet: Ordered queryset of nodes
"""Path-based tree storage using encoded paths to represent node positions in the hierarchy.
class MP_Node(Node):
"""
Materialized Path tree implementation.
Stores node position as encoded path strings.
Excellent for read-heavy workloads and large trees.
"""
# Built-in fields
path = models.CharField(max_length=255, unique=True)
depth = models.PositiveIntegerField()
numchild = models.PositiveIntegerField()
# Configuration attributes
steplen = 4 # Length of each path step
alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' # Encoding alphabet
node_order_by = [] # List of field names for ordering
gap = 1 # Gap between path values for insertions
objects = MP_NodeManager()
class Meta:
abstract = TrueCustomize the path encoding and behavior:
class Category(MP_Node):
name = models.CharField(max_length=50)
# Path configuration
steplen = 6 # Longer steps = deeper trees possible
alphabet = '0123456789ABCDEF' # Hex alphabet for compact paths
gap = 10 # Larger gaps = more insertion space
node_order_by = ['name'] # Automatic ordering by nameThe path field encodes the node's position using the configured alphabet:
0001, 0002, 0003, etc.00010001, 00010002 (parent_path + child_step)max_depth = 255 / steplenEnhanced manager and queryset with optimized deletion handling.
class MP_NodeManager(models.Manager):
"""Custom manager for Materialized Path trees."""
def get_queryset(self):
"""Returns MP_NodeQuerySet instance."""
class MP_NodeQuerySet(models.QuerySet):
"""
Custom queryset with enhanced delete method.
Handles descendant deletion efficiently for MP trees.
"""
def delete(self):
"""
Enhanced delete that properly handles MP tree structure.
Automatically deletes all descendants and updates tree.
"""Left/right boundary model using numerical boundaries to represent tree structure.
class NS_Node(Node):
"""
Nested Sets tree implementation.
Uses left/right boundaries to represent tree structure.
Optimal for complex tree queries and read operations.
"""
# Built-in fields
lft = models.PositiveIntegerField(db_index=True)
rgt = models.PositiveIntegerField(db_index=True)
tree_id = models.PositiveIntegerField(db_index=True)
depth = models.PositiveIntegerField(db_index=True)
# Configuration
node_order_by = [] # List of field names for ordering
objects = NS_NodeManager()
class Meta:
abstract = TrueNodes are represented by left (lft) and right (rgt) boundaries:
lft < child_lft < child_rgt < rgtEnhanced manager and queryset optimized for nested sets operations.
class NS_NodeManager(models.Manager):
"""Custom manager for Nested Sets trees."""
def get_queryset(self):
"""Returns NS_NodeQuerySet instance."""
class NS_NodeQuerySet(models.QuerySet):
"""
Custom queryset with enhanced delete method.
Handles gap closing after node deletion in nested sets.
"""
def delete(self, removed_ranges=None, deleted_counter=None):
"""
Enhanced delete with gap closing for nested sets.
Parameters:
removed_ranges (list, optional): List of removed boundary ranges
deleted_counter (dict, optional): Counter for deleted nodes
Automatically updates lft/rgt values after deletion.
"""# Adjacency List
class Category(AL_Node):
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE)
sib_order = models.PositiveIntegerField()
name = models.CharField(max_length=100)
node_order_by = ['name']
# Materialized Path
class Department(MP_Node):
name = models.CharField(max_length=100)
code = models.CharField(max_length=20)
node_order_by = ['code', 'name']
steplen = 4
# Nested Sets
class MenuItem(NS_Node):
title = models.CharField(max_length=100)
url = models.URLField(blank=True)
node_order_by = ['title']# In admin.py
from django.contrib import admin
from treebeard.admin import TreeAdmin
from .models import Category
@admin.register(Category)
class CategoryAdmin(TreeAdmin):
list_display = ['name', 'get_depth']
list_filter = ['depth']parent and sib_order fieldspath (unique), depth, and numchild fieldslft, rgt, tree_id, and depth fields (all included by default)Install with Tessl CLI
npx tessl i tessl/pypi-django-treebeard