Awesome Open Source
Awesome Open Source
Sponsorship

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.


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 a node and all its descendants:

obj.delete()

Delete the whole tree for the current node class:

cls.delete_tree()

Get a list with all ancestors (ordered from root to parent):

obj.get_ancestors()
# or
obj.ancestors

Get the ancestors count:

obj.get_ancestors_count()
# or
obj.ancestors_count

Get the ancestors queryset:

obj.get_ancestors_queryset()

Get the breadcrumbs to current node (included):

obj.get_breadcrumbs(attr=None)
# or
obj.breadcrumbs

Get a list containing all children:

obj.get_children()
# or
obj.children

Get the children count:

obj.get_children_count()
# or
obj.children_count

Get the children queryset:

obj.get_children_queryset()

Get the node depth (how many levels of descendants):

obj.get_depth()
# or
obj.depth

Get a list containing all descendants:

obj.get_descendants()
# or
obj.descendants

Get the descendants count:

obj.get_descendants_count()
# or
obj.descendants_count

Get the descendants queryset:

obj.get_descendants_queryset()

Get a n-dimensional dict representing the model tree:

obj.get_descendants_tree()
# or
obj.descendants_tree

Get a multiline string representing the model tree:

obj.get_descendants_tree_display()
# or
obj.descendants_tree_display

Get the first child node:

obj.get_first_child()
# or
obj.first_child

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

obj.get_index()
# or
obj.index

Get the last child node:

obj.get_last_child()
# or
obj.last_child

Get the node level (starting from 1):

obj.get_level()
# or
obj.level

Get the order value used for ordering:

obj.get_order()
# or
obj.order

Get the parent node:

obj.get_parent()
# or
obj.parent

Set the parent node:

obj.set_parent(parent_obj)

Get the node priority:

obj.get_priority()
# or
obj.priority

Set the node priority:

obj.set_priority(100)

Get the root node for the current node:

obj.get_root()
# or
obj.root

Get a list with all root nodes:

cls.get_roots()
# or
cls.roots

Get root nodes queryset:

cls.get_roots_queryset()

Get a list with all the siblings:

obj.get_siblings()
# or
obj.siblings

Get the siblings count:

obj.get_siblings_count()
# or
obj.siblings_count

Get the siblings queryset:

obj.get_siblings_queryset()

Get a n-dimensional dict representing the model tree:

cls.get_tree()
# or
cls.tree

Get a multiline string representing the model tree:

cls.get_tree_display()
# or
cls.tree_display

Return True if the current node is ancestor of target_obj:

obj.is_ancestor_of(target_obj)

Return True if the current node is child of target_obj:

obj.is_child_of(target_obj)

Return True if the current node is descendant of target_obj:

obj.is_descendant_of(target_obj)

Return True if the current node is the first child:

obj.is_first_child()

Return True if the current node is the last child:

obj.is_last_child()

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

obj.is_leaf()

Return True if the current node is parent of target_obj:

obj.is_parent_of(target_obj)

Return True if the current node is root:

obj.is_root()

Return True if the current node is root of target_obj:

obj.is_root_of(target_obj)

Return True if the current node is sibling of target_obj:

obj.is_sibling_of(target_obj)

Update tree manually, useful after bulk updates:

cls.update_tree()

Testing

# create python virtual environment
virtualenv testing_django_treenode

# activate virtualenv
cd testing_django_treenode && . bin/activate

# clone repo
git clone https://github.com/fabiocaccamo/django-treenode.git src && cd src

# install dependencies
pip install -r requirements.txt

# run tests
tox
# or
python setup.py test
# or
python -m django test --settings "tests.settings"

License

Released under MIT License.


See also

  • django-admin-interface - the default admin interface made customizable by the admin itself. popup windows replaced by modals. 🧙 ⚡

  • 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. 🇮🇹 💳


Get A Weekly Email With Trending Projects For These Topics
No Spam. Unsubscribe easily at any time.
python (48,438
django (931
node (910
tree (170
model (105
root (27
category (21
trees (15

Find Open Source By Browsing 7,000 Topics Across 59 Categories