Package: django-fast-treenode
Django-fast-treenodeCombination of Adjacency List and Closure TableFunctionsApplication for supporting tree (hierarchical) data structure in Django projectsfast: the fastest of the two methods is used to process requests, combining the advantages of anAdjacency Tableand aClosure Table,even faster: the main resource-intensive operations arecached;bulk operationsare used for inserts and changes,synchronized: model instances in memory are automatically updated,compatibility: you can easily add a tree node to existing projects using TreeNode without changing the code,no dependencies,easy setup: just extend the abstract model/model-admin,admin integration: visualization options (accordion, breadcrumbs or padding),widget: Built-in Select2-to-Tree extends Select2 to support arbitrary nesting levels.Debut ideaThis is a modification of the reusabledjango-treenodeapplication developed byFabio Caccamo.
The original application has significant advantages over other analogues, and indeed, is one of the best implementations of support for hierarchical structures for Django.Fabio's idea was to use the Adjacency List method to store the data tree. The most probable and time-consuming requests are calculated in advance and stored in the database. Also, most requests are cached. As a result, query processing is carried out in one call to the database or without it at all.However, this application has a number of undeniable shortcomings:the selected pre-calculations scheme entails high costs for adding a new element;inserting new elements uses signals, which leads to failures when using bulk-operations;the problem of ordering elements by priority inside the parent node has not been resolved.My idea was to solve these problems by combining the adjacency list with the Closure Table. Main advantages:the Closure Model is generated automatically;maintained compatibility with the original package at the level of documented functions;most requests are satisfied in one call to the database;inserting a new element takes two calls to the database without signals usage;bulk-operations are supported;the cost of creating a new dependency is reduced many times;useful functionality added for some methods (e.g.  theinclude_self=Falseanddepthparameters has been added to functions that return lists/querysets);additionally, the package includes a tree view widget for thetn_parentfield in the change form.Of course, at large levels of nesting, the use of the Closure Table leads to an increase in resource costs. But at the same time, the combined scheme still generally outperforms the original application in terms of performance.TheoryYou can get a basic understanding of what is a Closure Table from:presentationby Bill Karwin;articleby blogger Dirt Simple;articleby Andriy Zabavskyy.You can easily find additional information on your own on the Internet.Quick startRunpip install django-fast-treenodeAddtreenodetosettings.INSTALLED_APPSMake your model inherit fromtreenode.models.TreeNodeModel(described below)Make your model-admin inherit fromtreenode.admin.TreeNodeModelAdmin(described below)Run python manage.py makemigrations andpython manage.py migrateWhen updating an existing project, simply callcls.update_tree()function once.
It will automatically build a new and complete Closure Table for your tree.Configurationmodels.pyMake your model class inherit fromtreenode.models.TreeNodeModel:fromdjango.dbimportmodelsfromtreenode.modelsimportTreeNodeModelclassCategory(TreeNodeModel):# the field used to display the model instance# default value 'pk'treenode_display_field="name"name=models.CharField(max_length=50)classMeta(TreeNodeModel.Meta):verbose_name="Category"verbose_name_plural="Categories"TheTreeNodeModelabstract class adds many fields (prefixed withtn_to prevent direct access) and public methods to your models.admin.pyMake your model-admin class inherit fromtreenode.admin.TreeNodeModelAdmin.fromdjango.contribimportadminfromtreenode.adminimportTreeNodeModelAdminfromtreenode.formsimportTreeNodeFormfrom.modelsimportCategoryclassCategoryAdmin(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 choicesform=TreeNodeFormadmin.site.register(Category,CategoryAdmin)settings.pyYou can use a custom cache backend by adding atreenodeentry tosettings.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",},}forms.pyclass YoursForm(TreeNodeForm):

    class Meta:
        widgets = {
            'tn_parent': TreeWidget(attrs={'style': 'min-width:400px'}),
        }UsageMethods/Propertiesdeletedelete_treeget_ancestorsget_ancestors_countget_ancestors_pksget_ancestors_querysetget_breadcrumbsget_childrenget_children_countget_children_pksget_children_querysetget_depthget_descendantsget_descendants_countget_descendants_pksget_descendants_querysetget_descendants_treeget_descendants_tree_displayget_first_childget_indexget_last_childget_levelget_orderget_parentget_parent_pkset_parentget_pathget_priorityset_priorityget_rootget_root_pkget_rootsget_roots_querysetget_siblingsget_siblings_countget_siblings_pksget_siblings_querysetget_treeget_tree_displayis_ancestor_ofis_child_ofis_descendant_ofis_first_childis_last_childis_leafis_parent_ofis_rootis_root_ofis_sibling_ofupdate_treedeleteDelete a nodeifcascade=True(default behaviour), children and descendants will be deleted too,
otherwise children's parent will be set toNone(then children become roots):obj.delete(cascade=True)delete_treeDelete the whole treefor the current node class:cls.delete_tree()get_ancestorsGet alist with all ancestors(ordered from root to parent):obj.get_ancestors(include_self=True,depth=None)# orobj.ancestorsget_ancestors_countGet theancestors count:obj.get_ancestors_count(include_self=True,depth=None)# orobj.ancestors_countget_ancestors_pksGet theancestors pkslist:obj.get_ancestors_pks(include_self=True,depth=None)# orobj.ancestors_pksget_ancestors_querysetGet theancestors queryset(ordered from parent to root):obj.get_ancestors_queryset(include_self=True,depth=None)get_breadcrumbsGet thebreadcrumbsto current node (included):obj.get_breadcrumbs(attr=None)# orobj.breadcrumbsget_childrenGet alist containing all children:obj.get_children()# orobj.childrenget_children_countGet thechildren count:obj.get_children_count()# orobj.children_countget_children_pksGet thechildren pkslist:obj.get_children_pks()# orobj.children_pksget_children_querysetGet thechildren queryset:obj.get_children_queryset()get_depthGet thenode depth(how many levels of descendants):obj.get_depth()# orobj.depthget_descendantsGet alist containing all descendants:obj.get_descendants(include_self=False,depth=None)# orobj.descendantsget_descendants_countGet thedescendants count:obj.get_descendants_count(include_self=False,depth=None)# orobj.descendants_countget_descendants_pksGet thedescendants pkslist:obj.get_descendants_pks(include_self=False,depth=None)# orobj.descendants_pksget_descendants_querysetGet thedescendants queryset:obj.get_descendants_queryset(include_self=False,depth=None)get_descendants_treeGet an-dimensionaldictrepresenting themodel tree:obj.get_descendants_tree()# orobj.descendants_treeget_descendants_tree_displayGet amultilinestringrepresenting themodel tree:obj.get_descendants_tree_display(include_self=False,depth=None)# orobj.descendants_tree_displayget_first_childGet thefirst child node:obj.get_first_child()# orobj.first_childget_indexGet thenode index(index in node.parent.children list):obj.get_index()# orobj.indexget_last_childGet thelast child node:obj.get_last_child()# orobj.last_childget_levelGet thenode level(starting from 1):obj.get_level()# orobj.levelget_orderGet theorder valueused for ordering:obj.get_order()# orobj.orderget_parentGet theparent node:obj.get_parent()# orobj.parentget_parent_pkGet theparent node pk:obj.get_parent_pk()# orobj.parent_pkset_parentSet theparent node:obj.set_parent(parent_obj)get_priorityGet thenode priority:obj.get_priority()# orobj.priorityget_pathAdded the function of decorating amaterialized path. The path is formed according to the value of thetn_priorityfield.cls.get_path(prefix='',suffix='',delimiter='.',format_str='')set_prioritySet thenode priority:obj.set_priority(100)get_rootGet theroot nodefor the current node:obj.get_root()# orobj.rootget_root_pkGet theroot node pkfor the current node:obj.get_root_pk()# orobj.root_pkget_rootsGet alist with all root nodes:cls.get_roots()# orcls.rootsget_roots_querysetGetroot nodes queryset:cls.get_roots_queryset()get_siblingsGet alist with all the siblings:obj.get_siblings()# orobj.siblingsget_siblings_countGet thesiblings count:obj.get_siblings_count()# orobj.siblings_countget_siblings_pksGet thesiblings pkslist:obj.get_siblings_pks()# orobj.siblings_pksget_siblings_querysetGet thesiblings queryset:obj.get_siblings_queryset()get_treeReturns ann-dimensional dictionaryrepresenting the model tree. Each node
contains a "children"=[] key with a list of nested dictionaries of child nodes.:cls.get_tree(instance=None)# orcls.treeget_tree_displayGet amultilinestringrepresenting themodel tree:cls.get_tree_display()# orcls.tree_displayis_ancestor_ofReturnTrueif the current nodeis ancestorof target_obj:obj.is_ancestor_of(target_obj)is_child_ofReturnTrueif the current nodeis childof target_obj:obj.is_child_of(target_obj)is_descendant_ofReturnTrueif the current nodeis descendantof target_obj:obj.is_descendant_of(target_obj)is_first_childReturnTrueif the current node is thefirst child:obj.is_first_child()is_last_childReturnTrueif the current node is thelast child:obj.is_last_child()is_leafReturnTrueif the current node isleaf(it has not children):obj.is_leaf()is_parent_ofReturnTrueif the current nodeis parentof target_obj:obj.is_parent_of(target_obj)is_rootReturnTrueif the current nodeis root:obj.is_root()is_root_ofReturnTrueif the current nodeis rootof target_obj:obj.is_root_of(target_obj)is_sibling_ofReturnTrueif the current nodeis siblingof target_obj:obj.is_sibling_of(target_obj)update_treeUpdate treemanually, useful afterbulk updates:cls.update_tree()LicenseReleased underMIT License.CautionsThe provided code is already being used in production projects, even though I have only done general testing. That is why the risk of using the code lies entirely with you.Warning: don't access the tree node fields directly! Most of them have been removed as unnecessary. In the future, onlytn_parentandtn_prioritywill be kept. Use the functions described in the documentation above or the documentation for theoriginal application.CreditsThis software contains, uses, including in a modified form:django-treenodebyFabio Caccamo;Select2-to-TreeSelect2 extension byclivezhgSpecial thanks toMathieu Leplatrefor the advice used in writing this applicationTo doFuture plans:may be will add the ability to determine the priority of the parent by any field, for example, by creation date or alphabetical order;drug-and-drop support;to be happy, to don't worry, until die.Your wishes, objections, comments are welcome.