Skip to main content

probably the best abstract model/admin for your tree based stuff.

Project description

django-treenode

Probably the best abstract model / admin for your tree based stuff.

Features

  • Fast - get ancestors, children, descendants, parent, root, siblings, tree with no queries
  • Synced - in-memory model instances are automatically updated
  • Compatibility - you can easily add treenode to existing projects
  • No dependencies
  • Easy configuration - just extend the abstract model / model-admin
  • Admin integration - great tree visualization: accordion, breadcrumbs or indentation
indentation (default) breadcrumbs accordion
treenode-admin-display-mode-indentation treenode-admin-display-mode-breadcrumbs treenode-admin-display-mode-accordion

Installation

  • Run pip install django-treenode
  • Add treenode to settings.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 makemigrations and python 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'
    treenode_display_field = "name"

    name = models.CharField(max_length=50)

    class Meta(TreeNodeModel.Meta):
        verbose_name = "Category"
        verbose_name_plural = "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)
    treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_ACCORDION
    # treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_BREADCRUMBS
    # treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_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

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

get_ancestors_count

Get the ancestors count:

obj.get_ancestors_count()
# or
obj.ancestors_count

get_ancestors_pks

Get the ancestors pks list:

obj.get_ancestors_pks()
# or
obj.ancestors_pks

get_ancestors_queryset

Get the ancestors queryset (ordered from parent to root):

obj.get_ancestors_queryset()

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

get_children_count

Get the children count:

obj.get_children_count()
# or
obj.children_count

get_children_pks

Get the children pks list:

obj.get_children_pks()
# or
obj.children_pks

get_children_queryset

Get the children queryset:

obj.get_children_queryset()

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

get_descendants_count

Get the descendants count:

obj.get_descendants_count()
# or
obj.descendants_count

get_descendants_pks

Get the descendants pks list:

obj.get_descendants_pks()
# or
obj.descendants_pks

get_descendants_queryset

Get the descendants queryset:

obj.get_descendants_queryset()

get_descendants_tree

Get a n-dimensional dict representing the model tree:

obj.get_descendants_tree()
# or
obj.descendants_tree

get_descendants_tree_display

Get a multiline string representing the model tree:

obj.get_descendants_tree_display()
# or
obj.descendants_tree_display

get_first_child

Get the first child node:

obj.get_first_child()
# or
obj.first_child

get_index

Get the node index (index in node.parent.children list):

obj.get_index()
# or
obj.index

get_last_child

Get the last child node:

obj.get_last_child()
# 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

get_parent_pk

Get the parent node pk:

obj.get_parent_pk()
# or
obj.parent_pk

set_parent

Set the parent node:

obj.set_parent(parent_obj)

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

get_root_pk

Get the root node pk for the current node:

obj.get_root_pk()
# or
obj.root_pk

get_roots

Get a list with all root nodes:

cls.get_roots()
# or
cls.roots

get_roots_queryset

Get root nodes queryset:

cls.get_roots_queryset()

get_siblings

Get a list with all the siblings:

obj.get_siblings()
# or
obj.siblings

get_siblings_count

Get the siblings count:

obj.get_siblings_count()
# or
obj.siblings_count

get_siblings_pks

Get the siblings pks list:

obj.get_siblings_pks()
# or
obj.siblings_pks

get_siblings_queryset

Get the siblings queryset:

obj.get_siblings_queryset()

get_tree

Get a n-dimensional dict representing the model tree:

cls.get_tree()
# or
cls.tree

get_tree_display

Get a multiline string representing the model tree:

cls.get_tree_display()
# or
cls.tree_display

is_ancestor_of

Return True if the current node is ancestor of target_obj:

obj.is_ancestor_of(target_obj)

is_child_of

Return True if the current node is child of target_obj:

obj.is_child_of(target_obj)

is_descendant_of

Return True if the current node is descendant of target_obj:

obj.is_descendant_of(target_obj)

is_first_child

Return True if the current node is the first child:

obj.is_first_child()

is_last_child

Return True if the current node is the last child:

obj.is_last_child()

is_leaf

Return True if the current node is leaf (it has not children):

obj.is_leaf()

is_parent_of

Return True if the current node is parent of target_obj:

obj.is_parent_of(target_obj)

is_root

Return True if the current node is root:

obj.is_root()

is_root_of

Return True if the current node is root of target_obj:

obj.is_root_of(target_obj)

is_sibling_of

Return True if the current node is sibling of target_obj:

obj.is_sibling_of(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
  • :blue_heart: 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-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. 🧟‍♂️

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_treenode-0.23.3.tar.gz (27.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_treenode-0.23.3-py3-none-any.whl (22.0 kB view details)

Uploaded Python 3

File details

Details for the file django_treenode-0.23.3.tar.gz.

File metadata

  • Download URL: django_treenode-0.23.3.tar.gz
  • Upload date:
  • Size: 27.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for django_treenode-0.23.3.tar.gz
Algorithm Hash digest
SHA256 714c825d5b925a3d2848d0709f29973941ea41a606b8e2b64cbec46010a8cce3
MD5 8859acdac88f5d2eb848f4734e522163
BLAKE2b-256 255886edbbd1075bb8bc0962c6feb13bc06822405a10fea8352ad73ab2babdd9

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_treenode-0.23.3.tar.gz:

Publisher: create-release.yml on fabiocaccamo/django-treenode

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_treenode-0.23.3-py3-none-any.whl.

File metadata

File hashes

Hashes for django_treenode-0.23.3-py3-none-any.whl
Algorithm Hash digest
SHA256 8072e1ac688c1ed3ab95a98a797c5e965380de5228a389d60a4ef8b9a6449387
MD5 e1defad4cb0b7185fcdf97ba35e0d1b8
BLAKE2b-256 bc52696db237167483324ef38d8d090fb0fcc33dbb70ebe66c75868005fb7c75

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_treenode-0.23.3-py3-none-any.whl:

Publisher: create-release.yml on fabiocaccamo/django-treenode

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page