fabiocaccamo / django-treenode

:deciduous_tree: probably the best abstract model/admin for your tree based stuff.
MIT License
679 stars 33 forks source link
abstract ancestors categories category children descendants django model node nodes parent python root roots siblings tree trees

django-treenode

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

Features

indentation (default) breadcrumbs accordion
treenode-admin-display-mode-indentation treenode-admin-display-mode-breadcrumbs treenode-admin-display-mode-accordion

Installation

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

See also