:deciduous_tree: probably the best abstract model/admin for your tree based stuff.
django-treenode
Probably the best abstract model / admin for your tree based stuff.Features
- Fast - get
ancestors,children,descendants,parent,root,siblings,treewith no queries - Synced - in-memory model instances are automatically updated
- Compatibility - you can easily add
treenodeto existing projects - No dependencies
- Easy configuration - just extend the abstract model / model-admin
- Admin integration - great tree visualization: accordion, breadcrumbs or indentation
Installation
- Run
pip install django-treenode - Add
treenodetosettings.INSTALLED_APPS - Make your model inherit from
treenode.models.TreeNodeModel(described below) - Make your model-admin inherit from
treenode.admin.TreeNodeModelAdmin(described below) - Run
python manage.py makemigrationsandpython manage.py migrate
Configuration
models.py
Make your model class inherit from treenode.models.TreeNodeModel:
from django.db import models
from treenode.models import TreeNodeModel
class Category(TreeNodeModel):
# the field used to display the model instance # default value 'pk' treenodedisplayfield = "name"
name = models.CharField(max_length=50)
class Meta(TreeNodeModel.Meta): verbose_name = "Category" verbosenameplural = "Categories"
The TreeNodeModel abstract class adds many fields (prefixed with tn_ to prevent direct access) and public methods to your models.
:warning: If you are extending a model that already has some fields, please ensure that your model existing fields names don't clash with TreeNodeModel public methods/properties names.
admin.py
Make your model-admin class inherit from treenode.admin.TreeNodeModelAdmin.
from django.contrib import admin
from treenode.admin import TreeNodeModelAdmin from treenode.forms import TreeNodeForm
from .models import Category
class CategoryAdmin(TreeNodeModelAdmin):
# set the changelist display mode: 'accordion', 'breadcrumbs' or 'indentation' (default) # when changelist results are filtered by a querystring, # 'breadcrumbs' mode will be used (to preserve data display integrity) treenodedisplaymode = TreeNodeModelAdmin.TREENODEDISPLAYMODE_ACCORDION # treenodedisplaymode = TreeNodeModelAdmin.TREENODEDISPLAYMODE_BREADCRUMBS # treenodedisplaymode = TreeNodeModelAdmin.TREENODEDISPLAYMODE_INDENTATION
# use TreeNodeForm to automatically exclude invalid parent choices form = TreeNodeForm
admin.site.register(Category, CategoryAdmin)
settings.py
You can use a custom cache backend by adding a treenode entry to settings.CACHES, otherwise the default cache backend will be used.
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
"LOCATION": "...",
},
"treenode": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
},
}
Usage
Methods/Properties
-
delete -
deletetree -
getancestors -
getancestorscount -
getancestorspks -
getancestorsqueryset -
getbreadcrumbs -
getchildren -
getchildrencount -
getchildrenpks -
getchildrenqueryset -
getdepth -
getdescendants -
getdescendantscount -
getdescendantspks -
getdescendantsqueryset -
getdescendantstree -
getdescendantstreedisplay -
getfirstchild -
getindex -
getlastchild -
getlevel -
getorder -
getparent -
getparentpk -
setparent -
getpriority -
setpriority -
getroot -
getrootpk -
getroots -
getrootsqueryset -
getsiblings -
getsiblingscount -
getsiblingspks -
getsiblingsqueryset -
gettree -
gettreedisplay -
isancestorof -
ischildof -
isdescendantof -
isfirstchild -
islastchild -
isleaf -
isparentof -
isroot -
isrootof -
issiblingof -
updatetree
delete
Delete a node if cascade=True (default behaviour), children and descendants will be deleted too,
otherwise children's parent will be set to None (then children become roots):
obj.delete(cascade=True)
delete_tree
Delete the whole tree for the current node class:
cls.delete_tree()
get_ancestors
Get a list with all ancestors (ordered from root to parent):
obj.get_ancestors()
or
obj.ancestors
getancestorscount
Get the ancestors count:
obj.getancestorscount()
or
obj.ancestors_count
getancestorspks
Get the ancestors pks list:
obj.getancestorspks()
or
obj.ancestors_pks
getancestorsqueryset
Get the ancestors queryset (ordered from parent to root):
obj.getancestorsqueryset()
get_breadcrumbs
Get the breadcrumbs to current node (included):
obj.get_breadcrumbs(attr=None)
or
obj.breadcrumbs
get_children
Get a list containing all children:
obj.get_children()
or
obj.children
getchildrencount
Get the children count:
obj.getchildrencount()
or
obj.children_count
getchildrenpks
Get the children pks list:
obj.getchildrenpks()
or
obj.children_pks
getchildrenqueryset
Get the children queryset:
obj.getchildrenqueryset()
get_depth
Get the node depth (how many levels of descendants):
obj.get_depth()
or
obj.depth
get_descendants
Get a list containing all descendants:
obj.get_descendants()
or
obj.descendants
getdescendantscount
Get the descendants count:
obj.getdescendantscount()
or
obj.descendants_count
getdescendantspks
Get the descendants pks list:
obj.getdescendantspks()
or
obj.descendants_pks
getdescendantsqueryset
Get the descendants queryset:
obj.getdescendantsqueryset()
getdescendantstree
Get a n-dimensional dict representing the model tree:
obj.getdescendantstree()
or
obj.descendants_tree
getdescendantstree_display
Get a multiline string representing the model tree:
obj.getdescendantstree_display()
or
obj.descendantstreedisplay
getfirstchild
Get the first child node:
obj.getfirstchild()
or
obj.first_child
get_index
Get the node index (index in node.parent.children list):
obj.get_index()
or
obj.index
getlastchild
Get the last child node:
obj.getlastchild()
or
obj.last_child
get_level
Get the node level (starting from 1):
obj.get_level()
or
obj.level
get_order
Get the order value used for ordering:
obj.get_order()
or
obj.order
get_parent
Get the parent node:
obj.get_parent()
or
obj.parent
getparentpk
Get the parent node pk:
obj.getparentpk()
or
obj.parent_pk
set_parent
Set the parent node:
obj.setparent(parentobj)
get_priority
Get the node priority:
obj.get_priority()
or
obj.priority
set_priority
Set the node priority:
obj.set_priority(100)
get_root
Get the root node for the current node:
obj.get_root()
or
obj.root
getrootpk
Get the root node pk for the current node:
obj.getrootpk()
or
obj.root_pk
get_roots
Get a list with all root nodes:
cls.get_roots()
or
cls.roots
getrootsqueryset
Get root nodes queryset:
cls.getrootsqueryset()
get_siblings
Get a list with all the siblings:
obj.get_siblings()
or
obj.siblings
getsiblingscount
Get the siblings count:
obj.getsiblingscount()
or
obj.siblings_count
getsiblingspks
Get the siblings pks list:
obj.getsiblingspks()
or
obj.siblings_pks
getsiblingsqueryset
Get the siblings queryset:
obj.getsiblingsqueryset()
get_tree
Get a n-dimensional dict representing the model tree:
cls.get_tree()
or
cls.tree
gettreedisplay
Get a multiline string representing the model tree:
cls.gettreedisplay()
or
cls.tree_display
isancestorof
Return True if the current node is ancestor of target_obj:
obj.isancestorof(target_obj)
ischildof
Return True if the current node is child of target_obj:
obj.ischildof(target_obj)
isdescendantof
Return True if the current node is descendant of target_obj:
obj.isdescendantof(target_obj)
isfirstchild
Return True if the current node is the first child:
obj.isfirstchild()
islastchild
Return True if the current node is the last child:
obj.islastchild()
is_leaf
Return True if the current node is leaf (it has not children):
obj.is_leaf()
isparentof
Return True if the current node is parent of target_obj:
obj.isparentof(target_obj)
is_root
Return True if the current node is root:
obj.is_root()
isrootof
Return True if the current node is root of target_obj:
obj.isrootof(target_obj)
issiblingof
Return True if the current node is sibling of target_obj:
obj.issiblingof(target_obj)
update_tree
Update tree manually, useful after bulk updates:
cls.update_tree()
Bulk Operations
To perform bulk operations it is recommended to turn off signals, then triggering the tree update at the end:
from treenode.signals import no_signals
with no_signals(): # execute custom bulk operations pass
trigger tree update only once
YourModel.update_tree()
FAQ
Custom tree serialization
How can I serialize a tree using a custom data structure?
This has been discussed here.
Testing
# clone repository
git clone https://github.com/fabiocaccamo/django-treenode.git && cd django-treenode
create virtualenv and activate it
python -m venv venv && . venv/bin/activate
upgrade pip
python -m pip install --upgrade pip
install requirements
pip install -r requirements.txt -r requirements-test.txt
install pre-commit to run formatters and linters
pre-commit install --install-hooks
run tests
tox
or
python runtests.py
or
python -m django test --settings "tests.settings"
License
Released under MIT License.Supporting
- :star: Star this project on GitHub
- :octocat: Follow me on GitHub
- :blueheart: Follow me on Bluesky
- :moneybag: Sponsor me on Github
See also
django-admin-interface- the default admin interface made customizable by the admin itself. popup windows replaced by modals. ๐ง โก
django-cache-cleaner- clear the entire cache or individual caches easily using the admin panel or management command. ๐งนโจ
django-colorfield- simple color field for models with a nice color-picker in the admin. ๐จ
django-email-validators- no more invalid or disposable emails in your database. โ๏ธ โ
django-extra-settings- config and manage typed extra settings using just the django admin. โ๏ธ
django-maintenance-mode- shows a 503 error page when maintenance-mode is on. ๐ง ๐ ๏ธ
django-redirects- redirects with full control. โช๏ธ
python-benedict- dict subclass with keylist/keypath support, I/O shortcuts (base64, csv, json, pickle, plist, query-string, toml, xml, yaml) and many utilities. ๐
python-codicefiscale- encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. ๐ฎ๐น ๐ณ
python-fontbro- friendly font operations. ๐งข
python-fsutil- file-system utilities for lazy devs. ๐งโโ๏ธ