django-ltree-xtd
PostgreSQL ltree-based tree models for Django with trigger-maintained paths.
Provides two abstract model mixins that use PostgreSQL's native ltree extension for efficient hierarchical data, with database triggers (via django-pgtrigger) to automatically maintain tree paths.
Features
- Native PostgreSQL ltree for fast hierarchical queries using GiST indexes
- Trigger-maintained paths — paths are computed and cascaded automatically by the database
- Two API flavors:
LTreeTreebeardMixin— API compatible with django-treebeard'sMP_NodeLTreeMPTTMixin— API compatible with django-mptt'sMPTTModel
- PK-based paths (e.g.,
1.42.103) — no slug collisions, no label length limits - No application-level path management — the database enforces integrity
Requirements
- Python 3.10+
- Django 4.2+
- PostgreSQL with the
ltreeextension - django-pgtrigger 4.11+
Installation
pip install django-ltree-xtd
Add to INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"ltree",
# ...
]
Run migrations to create the ltree extension:
python manage.py migrate ltree
Quick Start
Using the Treebeard-compatible API
from django.db import models
from ltree.models import LTreeTreebeardMixin
class Category(LTreeTreebeardMixin):
name = models.CharField(max_length=255)
# Create tree
root = Category.add_root(name="Electronics")
laptops = root.add_child(name="Laptops")
phones = root.add_child(name="Phones")
gaming = laptops.add_child(name="Gaming Laptops")
# Navigate
root.get_children() # [laptops, phones]
root.get_descendants() # [laptops, phones, gaming]
gaming.get_ancestors() # [root, laptops]
gaming.get_root() # root
# Check relationships
gaming.is_descendant_of(root) # True
root.is_leaf() # False
gaming.is_leaf() # True
# Bulk operations
data = Category.dump_bulk()
Category.load_bulk(data)
Using the MPTT-compatible API
from django.db import models
from ltree.models import LTreeMPTTMixin
class Tag(LTreeMPTTMixin):
name = models.CharField(max_length=255)
# Create tree
root = Tag.objects.create(name="Root", parent=None)
child = Tag.objects.create(name="Child", parent=root)
grandchild = Tag.objects.create(name="Grandchild", parent=child)
# Navigate (MPTT-style)
grandchild.get_ancestors() # [root, child]
grandchild.get_ancestors(ascending=True) # [child, root]
root.get_descendants(include_self=True) # [root, child, grandchild]
root.get_family() # [root, child, grandchild]
root.get_leafnodes() # [grandchild]
grandchild.get_level() # 2
# Move nodes
grandchild.move_to(root, position="first-child")
# Boolean checks
root.is_root_node() # True
grandchild.is_leaf_node() # True
grandchild.is_child_node() # True
# Manager methods
Tag.objects.root_nodes()
Tag.objects.get_queryset_descendants(some_queryset)
Tag.objects.get_queryset_ancestors(some_queryset)
How It Works
Both mixins provide:
- A
parentForeignKey to self - An
pathLTreeField that stores the materialized path
Two PostgreSQL triggers maintain the paths:
compute_path— On INSERT or UPDATE ofparent_id, computes the node's path from its parent's path + its own PKupdate_descendants— After a path change, cascades the update to all descendants
This means moving a subtree is a single UPDATE that triggers automatic cascading — no application-level tree rebuilding needed.
License
MIT
Metadata
Release files for django-ltree-xtd 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_ltree_xtd-0.1.0.tar.gz | 15.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_ltree_xtd-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 27.6 kB
Release files / django_ltree_xtd-0.1.0.tar.gz
| Download URL | django_ltree_xtd-0.1.0.tar.gz |
|---|---|
| Size | 15.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
71adfddb6d52804ed69b6a43f091ec2ff3a5899eed5a6814ef9ffaaf4c5376ee
|
|
BLAKE2b-256 checksum How to use checksums |
2917e6e16a8eb8bd4f3361f2a1128fb29dc39da4a2c959bb0fe607e02ba5fef4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 3, 2026.
Transparency logRelease files / django_ltree_xtd-0.1.0-py3-none-any.whl
| Download URL | django_ltree_xtd-0.1.0-py3-none-any.whl |
|---|---|
| Size | 12.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
96ee39b8fdd0c36959af37e8dbaf63df9534dd8f5b16505323e30f4bd3f0afdf
|
|
BLAKE2b-256 checksum How to use checksums |
72e88918b466df0708555d0ec118b9ba3fd6b5dd89477e8d2000f17edeeaac99
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 3, 2026.
Transparency log