django-treenode


Namedjango-treenode JSON
Version 0.22.0 PyPI version JSON
download
home_page
Summaryprobably the best abstract model/admin for your tree based stuff.
upload_time2024-01-31 13:37:31
maintainer
docs_urlNone
author
requires_python
licenseMIT License Copyright (c) 2018 Fabio Caccamo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords python django trees tree nodes node categories category ancestors parents children descendants siblings abstract model
VCS
bugtrack_url
requirements django
Travis-CI No Travis.
coveralls test coverage No coveralls.
            [![](https://img.shields.io/pypi/pyversions/django-treenode.svg?color=3776AB&logo=python&logoColor=white)](https://www.python.org/)
[![](https://img.shields.io/pypi/djversions/django-treenode?color=0C4B33&logo=django&logoColor=white&label=django)](https://www.djangoproject.com/)

[![](https://img.shields.io/pypi/v/django-treenode.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-treenode/)
[![](https://static.pepy.tech/badge/django-treenode/month)](https://pepy.tech/project/django-treenode)
[![](https://img.shields.io/github/stars/fabiocaccamo/django-treenode?logo=github&style=flat)](https://github.com/fabiocaccamo/django-treenode/stargazers)
[![](https://img.shields.io/pypi/l/django-treenode.svg?color=blue)](https://github.com/fabiocaccamo/django-treenode/blob/main/LICENSE.txt)

[![](https://results.pre-commit.ci/badge/github/fabiocaccamo/django-treenode/main.svg)](https://results.pre-commit.ci/latest/github/fabiocaccamo/django-treenode/main)
[![](https://img.shields.io/github/actions/workflow/status/fabiocaccamo/django-treenode/test-package.yml?branch=main&label=build&logo=github)](https://github.com/fabiocaccamo/django-treenode)
[![](https://img.shields.io/codecov/c/gh/fabiocaccamo/django-treenode?logo=codecov)](https://codecov.io/gh/fabiocaccamo/django-treenode)
[![](https://img.shields.io/codacy/grade/0c79c196e5c9411babbaf5e8e5f7469c?logo=codacy)](https://www.codacy.com/app/fabiocaccamo/django-treenode)
[![](https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&logoColor=black)](https://github.com/psf/black)
[![](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

# 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-indentation] | ![treenode-admin-display-mode-breadcrumbs][treenode-admin-display-mode-breadcrumbs] | ![treenode-admin-display-mode-accordion][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`:

```python
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](#methodsproperties) names.**

---

### `admin.py`
Make your model-admin class inherit from `treenode.admin.TreeNodeModelAdmin`.

```python
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.

```python
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
        "LOCATION": "...",
    },
    "treenode": {
        "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
    },
}
```

## Usage

### Methods/Properties

-   [`delete`](#delete)
-   [`delete_tree`](#delete_tree)
-   [`get_ancestors`](#get_ancestors)
-   [`get_ancestors_count`](#get_ancestors_count)
-   [`get_ancestors_pks`](#get_ancestors_pks)
-   [`get_ancestors_queryset`](#get_ancestors_queryset)
-   [`get_breadcrumbs`](#get_breadcrumbs)
-   [`get_children`](#get_children)
-   [`get_children_count`](#get_children_count)
-   [`get_children_pks`](#get_children_pks)
-   [`get_children_queryset`](#get_children_queryset)
-   [`get_depth`](#get_depth)
-   [`get_descendants`](#get_descendants)
-   [`get_descendants_count`](#get_descendants_count)
-   [`get_descendants_pks`](#get_descendants_pks)
-   [`get_descendants_queryset`](#get_descendants_queryset)
-   [`get_descendants_tree`](#get_descendants_tree)
-   [`get_descendants_tree_display`](#get_descendants_tree_display)
-   [`get_first_child`](#get_first_child)
-   [`get_index`](#get_index)
-   [`get_last_child`](#get_last_child)
-   [`get_level`](#get_level)
-   [`get_order`](#get_order)
-   [`get_parent`](#get_parent)
-   [`get_parent_pk`](#get_parent_pk)
-   [`set_parent`](#set_parent)
-   [`get_priority`](#get_priority)
-   [`set_priority`](#set_priority)
-   [`get_root`](#get_root)
-   [`get_root_pk`](#get_root_pk)
-   [`get_roots`](#get_roots)
-   [`get_roots_queryset`](#get_roots_queryset)
-   [`get_siblings`](#get_siblings)
-   [`get_siblings_count`](#get_siblings_count)
-   [`get_siblings_pks`](#get_siblings_pks)
-   [`get_siblings_queryset`](#get_siblings_queryset)
-   [`get_tree`](#get_tree)
-   [`get_tree_display`](#get_tree_display)
-   [`is_ancestor_of`](#is_ancestor_of)
-   [`is_child_of`](#is_child_of)
-   [`is_descendant_of`](#is_descendant_of)
-   [`is_first_child`](#is_first_child)
-   [`is_last_child`](#is_last_child)
-   [`is_leaf`](#is_leaf)
-   [`is_parent_of`](#is_parent_of)
-   [`is_root`](#is_root)
-   [`is_root_of`](#is_root_of)
-   [`is_sibling_of`](#is_sibling_of)
-   [`update_tree`](#update_tree)


#### `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):
```python
obj.delete(cascade=True)
```

#### `delete_tree`
**Delete the whole tree** for the current node class:
```python
cls.delete_tree()
```

#### `get_ancestors`
Get a **list with all ancestors** (ordered from root to parent):
```python
obj.get_ancestors()
# or
obj.ancestors
```

#### `get_ancestors_count`
Get the **ancestors count**:
```python
obj.get_ancestors_count()
# or
obj.ancestors_count
```

#### `get_ancestors_pks`
Get the **ancestors pks** list:
```python
obj.get_ancestors_pks()
# or
obj.ancestors_pks
```

#### `get_ancestors_queryset`
Get the **ancestors queryset** (ordered from parent to root):
```python
obj.get_ancestors_queryset()
```

#### `get_breadcrumbs`
Get the **breadcrumbs** to current node (included):
```python
obj.get_breadcrumbs(attr=None)
# or
obj.breadcrumbs
```

#### `get_children`
Get a **list containing all children**:
```python
obj.get_children()
# or
obj.children
```

#### `get_children_count`
Get the **children count**:
```python
obj.get_children_count()
# or
obj.children_count
```

#### `get_children_pks`
Get the **children pks** list:
```python
obj.get_children_pks()
# or
obj.children_pks
```

#### `get_children_queryset`
Get the **children queryset**:
```python
obj.get_children_queryset()
```

#### `get_depth`
Get the **node depth** (how many levels of descendants):
```python
obj.get_depth()
# or
obj.depth
```

#### `get_descendants`
Get a **list containing all descendants**:
```python
obj.get_descendants()
# or
obj.descendants
```

#### `get_descendants_count`
Get the **descendants count**:
```python
obj.get_descendants_count()
# or
obj.descendants_count
```

#### `get_descendants_pks`
Get the **descendants pks** list:
```python
obj.get_descendants_pks()
# or
obj.descendants_pks
```

#### `get_descendants_queryset`
Get the **descendants queryset**:
```python
obj.get_descendants_queryset()
```

#### `get_descendants_tree`
Get a **n-dimensional** `dict` representing the **model tree**:
```python
obj.get_descendants_tree()
# or
obj.descendants_tree
```

#### `get_descendants_tree_display`
Get a **multiline** `string` representing the **model tree**:
```python
obj.get_descendants_tree_display()
# or
obj.descendants_tree_display
```

#### `get_first_child`
Get the **first child node**:
```python
obj.get_first_child()
# or
obj.first_child
```

#### `get_index`
Get the **node index** (index in node.parent.children list):
```python
obj.get_index()
# or
obj.index
```

#### `get_last_child`
Get the **last child node**:
```python
obj.get_last_child()
# or
obj.last_child
```

#### `get_level`
Get the **node level** (starting from 1):
```python
obj.get_level()
# or
obj.level
```

#### `get_order`
Get the **order value** used for ordering:
```python
obj.get_order()
# or
obj.order
```

#### `get_parent`
Get the **parent node**:
```python
obj.get_parent()
# or
obj.parent
```

#### `get_parent_pk`
Get the **parent node pk**:
```python
obj.get_parent_pk()
# or
obj.parent_pk
```

#### `set_parent`
Set the **parent node**:
```python
obj.set_parent(parent_obj)
```

#### `get_priority`
Get the **node priority**:
```python
obj.get_priority()
# or
obj.priority
```

#### `set_priority`
Set the **node priority**:
```python
obj.set_priority(100)
```

#### `get_root`
Get the **root node** for the current node:
```python
obj.get_root()
# or
obj.root
```

#### `get_root_pk`
Get the **root node pk** for the current node:
```python
obj.get_root_pk()
# or
obj.root_pk
```

#### `get_roots`
Get a **list with all root nodes**:
```python
cls.get_roots()
# or
cls.roots
```

#### `get_roots_queryset`
Get **root nodes queryset**:
```python
cls.get_roots_queryset()
```

#### `get_siblings`
Get a **list with all the siblings**:
```python
obj.get_siblings()
# or
obj.siblings
```

#### `get_siblings_count`
Get the **siblings count**:
```python
obj.get_siblings_count()
# or
obj.siblings_count
```

#### `get_siblings_pks`
Get the **siblings pks** list:
```python
obj.get_siblings_pks()
# or
obj.siblings_pks
```

#### `get_siblings_queryset`
Get the **siblings queryset**:
```python
obj.get_siblings_queryset()
```

#### `get_tree`
Get a **n-dimensional** `dict` representing the **model tree**:
```python
cls.get_tree()
# or
cls.tree
```

#### `get_tree_display`
Get a **multiline** `string` representing the **model tree**:
```python
cls.get_tree_display()
# or
cls.tree_display
```

#### `is_ancestor_of`
Return `True` if the current node **is ancestor** of target_obj:
```python
obj.is_ancestor_of(target_obj)
```

#### `is_child_of`
Return `True` if the current node **is child** of target_obj:
```python
obj.is_child_of(target_obj)
```

#### `is_descendant_of`
Return `True` if the current node **is descendant** of target_obj:
```python
obj.is_descendant_of(target_obj)
```

#### `is_first_child`
Return `True` if the current node is the **first child**:
```python
obj.is_first_child()
```

#### `is_last_child`
Return `True` if the current node is the **last child**:
```python
obj.is_last_child()
```

#### `is_leaf`
Return `True` if the current node is **leaf** (it has not children):
```python
obj.is_leaf()
```

#### `is_parent_of`
Return `True` if the current node **is parent** of target_obj:
```python
obj.is_parent_of(target_obj)
```

#### `is_root`
Return `True` if the current node **is root**:
```python
obj.is_root()
```

#### `is_root_of`
Return `True` if the current node **is root** of target_obj:
```python
obj.is_root_of(target_obj)
```

#### `is_sibling_of`
Return `True` if the current node **is sibling** of target_obj:
```python
obj.is_sibling_of(target_obj)
```

#### `update_tree`
**Update tree** manually, useful after **bulk updates**:
```python
cls.update_tree()
```

### Bulk Operations

To perform bulk operations it is recommended to turn off signals, then triggering the tree update at the end:

```python
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](https://github.com/fabiocaccamo/django-treenode/discussions/89#discussioncomment-5521654).

## Testing
```bash
# 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](LICENSE.txt).

---

## Supporting

- :star: Star this project on [GitHub](https://github.com/fabiocaccamo/django-treenode)
- :octocat: Follow me on [GitHub](https://github.com/fabiocaccamo)
- :blue_heart: Follow me on [Twitter](https://twitter.com/fabiocaccamo)
- :moneybag: Sponsor me on [Github](https://github.com/sponsors/fabiocaccamo)

## See also

- [`django-admin-interface`](https://github.com/fabiocaccamo/django-admin-interface) - the default admin interface made customizable by the admin itself. popup windows replaced by modals. ๐Ÿง™ โšก

- [`django-colorfield`](https://github.com/fabiocaccamo/django-colorfield) - simple color field for models with a nice color-picker in the admin. ๐ŸŽจ

- [`django-extra-settings`](https://github.com/fabiocaccamo/django-extra-settings) - config and manage typed extra settings using just the django admin. โš™๏ธ

- [`django-maintenance-mode`](https://github.com/fabiocaccamo/django-maintenance-mode) - shows a 503 error page when maintenance-mode is on. ๐Ÿšง ๐Ÿ› ๏ธ

- [`django-redirects`](https://github.com/fabiocaccamo/django-redirects) - redirects with full control. โ†ช๏ธ

- [`python-benedict`](https://github.com/fabiocaccamo/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`](https://github.com/fabiocaccamo/python-codicefiscale) - encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ’ณ

- [`python-fontbro`](https://github.com/fabiocaccamo/python-fontbro) - friendly font operations. ๐Ÿงข

- [`python-fsutil`](https://github.com/fabiocaccamo/python-fsutil) - file-system utilities for lazy devs. ๐ŸงŸโ€โ™‚๏ธ

[treenode-admin-display-mode-accordion]: https://user-images.githubusercontent.com/1035294/54942407-5040ec00-4f2f-11e9-873b-d0b3b521f534.png
[treenode-admin-display-mode-breadcrumbs]: https://user-images.githubusercontent.com/1035294/54942410-50d98280-4f2f-11e9-8a8b-a1ac6208398a.png
[treenode-admin-display-mode-indentation]: https://user-images.githubusercontent.com/1035294/54942411-50d98280-4f2f-11e9-9daf-d8339dd7a159.png

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "django-treenode",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "Fabio Caccamo <fabio.caccamo@gmail.com>",
    "keywords": "python,django,trees,tree,nodes,node,categories,category,ancestors,parents,children,descendants,siblings,abstract,model",
    "author": "",
    "author_email": "Fabio Caccamo <fabio.caccamo@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/ff/4b/ab32e40d6bc68373a7fb058f9b2365bbc7e0811bc5337a92d8aa5d9575d9/django-treenode-0.22.0.tar.gz",
    "platform": null,
    "description": "[![](https://img.shields.io/pypi/pyversions/django-treenode.svg?color=3776AB&logo=python&logoColor=white)](https://www.python.org/)\n[![](https://img.shields.io/pypi/djversions/django-treenode?color=0C4B33&logo=django&logoColor=white&label=django)](https://www.djangoproject.com/)\n\n[![](https://img.shields.io/pypi/v/django-treenode.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-treenode/)\n[![](https://static.pepy.tech/badge/django-treenode/month)](https://pepy.tech/project/django-treenode)\n[![](https://img.shields.io/github/stars/fabiocaccamo/django-treenode?logo=github&style=flat)](https://github.com/fabiocaccamo/django-treenode/stargazers)\n[![](https://img.shields.io/pypi/l/django-treenode.svg?color=blue)](https://github.com/fabiocaccamo/django-treenode/blob/main/LICENSE.txt)\n\n[![](https://results.pre-commit.ci/badge/github/fabiocaccamo/django-treenode/main.svg)](https://results.pre-commit.ci/latest/github/fabiocaccamo/django-treenode/main)\n[![](https://img.shields.io/github/actions/workflow/status/fabiocaccamo/django-treenode/test-package.yml?branch=main&label=build&logo=github)](https://github.com/fabiocaccamo/django-treenode)\n[![](https://img.shields.io/codecov/c/gh/fabiocaccamo/django-treenode?logo=codecov)](https://codecov.io/gh/fabiocaccamo/django-treenode)\n[![](https://img.shields.io/codacy/grade/0c79c196e5c9411babbaf5e8e5f7469c?logo=codacy)](https://www.codacy.com/app/fabiocaccamo/django-treenode)\n[![](https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&logoColor=black)](https://github.com/psf/black)\n[![](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n\n# django-treenode\nProbably the best abstract model / admin for your **tree** based stuff.\n\n## Features\n-   **Fast** - get `ancestors`, `children`, `descendants`, `parent`, `root`, `siblings`, `tree` with **no queries**\n-   **Synced** - in-memory model instances are automatically updated\n-   **Compatibility** - you can easily add `treenode` to existing projects\n-   **No dependencies**\n-   **Easy configuration** - just extend the abstract model / model-admin\n-   **Admin integration** - great tree visualization: **accordion**, **breadcrumbs** or **indentation**\n\n| indentation (default) | breadcrumbs | accordion |\n| --- | --- | --- |\n| ![treenode-admin-display-mode-indentation][treenode-admin-display-mode-indentation] | ![treenode-admin-display-mode-breadcrumbs][treenode-admin-display-mode-breadcrumbs] | ![treenode-admin-display-mode-accordion][treenode-admin-display-mode-accordion] |\n\n## Installation\n-   Run `pip install django-treenode`\n-   Add `treenode` to `settings.INSTALLED_APPS`\n-   Make your model inherit from `treenode.models.TreeNodeModel` *(described below)*\n-   Make your model-admin inherit from `treenode.admin.TreeNodeModelAdmin` *(described below)*\n-   Run `python manage.py makemigrations` and `python manage.py migrate`\n\n## Configuration\n### `models.py`\nMake your model class inherit from `treenode.models.TreeNodeModel`:\n\n```python\nfrom django.db import models\n\nfrom treenode.models import TreeNodeModel\n\n\nclass Category(TreeNodeModel):\n\n    # the field used to display the model instance\n    # default value 'pk'\n    treenode_display_field = \"name\"\n\n    name = models.CharField(max_length=50)\n\n    class Meta(TreeNodeModel.Meta):\n        verbose_name = \"Category\"\n        verbose_name_plural = \"Categories\"\n```\n\nThe `TreeNodeModel` abstract class adds many fields (prefixed with `tn_` to prevent direct access) and public methods to your models.\n\n: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](#methodsproperties) names.**\n\n---\n\n### `admin.py`\nMake your model-admin class inherit from `treenode.admin.TreeNodeModelAdmin`.\n\n```python\nfrom django.contrib import admin\n\nfrom treenode.admin import TreeNodeModelAdmin\nfrom treenode.forms import TreeNodeForm\n\nfrom .models import Category\n\n\nclass CategoryAdmin(TreeNodeModelAdmin):\n\n    # set the changelist display mode: 'accordion', 'breadcrumbs' or 'indentation' (default)\n    # when changelist results are filtered by a querystring,\n    # 'breadcrumbs' mode will be used (to preserve data display integrity)\n    treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_ACCORDION\n    # treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_BREADCRUMBS\n    # treenode_display_mode = TreeNodeModelAdmin.TREENODE_DISPLAY_MODE_INDENTATION\n\n    # use TreeNodeForm to automatically exclude invalid parent choices\n    form = TreeNodeForm\n\nadmin.site.register(Category, CategoryAdmin)\n```\n\n---\n\n### `settings.py`\nYou can use a custom cache backend by adding a `treenode` entry to `settings.CACHES`, otherwise the default cache backend will be used.\n\n```python\nCACHES = {\n    \"default\": {\n        \"BACKEND\": \"django.core.cache.backends.filebased.FileBasedCache\",\n        \"LOCATION\": \"...\",\n    },\n    \"treenode\": {\n        \"BACKEND\": \"django.core.cache.backends.locmem.LocMemCache\",\n    },\n}\n```\n\n## Usage\n\n### Methods/Properties\n\n-   [`delete`](#delete)\n-   [`delete_tree`](#delete_tree)\n-   [`get_ancestors`](#get_ancestors)\n-   [`get_ancestors_count`](#get_ancestors_count)\n-   [`get_ancestors_pks`](#get_ancestors_pks)\n-   [`get_ancestors_queryset`](#get_ancestors_queryset)\n-   [`get_breadcrumbs`](#get_breadcrumbs)\n-   [`get_children`](#get_children)\n-   [`get_children_count`](#get_children_count)\n-   [`get_children_pks`](#get_children_pks)\n-   [`get_children_queryset`](#get_children_queryset)\n-   [`get_depth`](#get_depth)\n-   [`get_descendants`](#get_descendants)\n-   [`get_descendants_count`](#get_descendants_count)\n-   [`get_descendants_pks`](#get_descendants_pks)\n-   [`get_descendants_queryset`](#get_descendants_queryset)\n-   [`get_descendants_tree`](#get_descendants_tree)\n-   [`get_descendants_tree_display`](#get_descendants_tree_display)\n-   [`get_first_child`](#get_first_child)\n-   [`get_index`](#get_index)\n-   [`get_last_child`](#get_last_child)\n-   [`get_level`](#get_level)\n-   [`get_order`](#get_order)\n-   [`get_parent`](#get_parent)\n-   [`get_parent_pk`](#get_parent_pk)\n-   [`set_parent`](#set_parent)\n-   [`get_priority`](#get_priority)\n-   [`set_priority`](#set_priority)\n-   [`get_root`](#get_root)\n-   [`get_root_pk`](#get_root_pk)\n-   [`get_roots`](#get_roots)\n-   [`get_roots_queryset`](#get_roots_queryset)\n-   [`get_siblings`](#get_siblings)\n-   [`get_siblings_count`](#get_siblings_count)\n-   [`get_siblings_pks`](#get_siblings_pks)\n-   [`get_siblings_queryset`](#get_siblings_queryset)\n-   [`get_tree`](#get_tree)\n-   [`get_tree_display`](#get_tree_display)\n-   [`is_ancestor_of`](#is_ancestor_of)\n-   [`is_child_of`](#is_child_of)\n-   [`is_descendant_of`](#is_descendant_of)\n-   [`is_first_child`](#is_first_child)\n-   [`is_last_child`](#is_last_child)\n-   [`is_leaf`](#is_leaf)\n-   [`is_parent_of`](#is_parent_of)\n-   [`is_root`](#is_root)\n-   [`is_root_of`](#is_root_of)\n-   [`is_sibling_of`](#is_sibling_of)\n-   [`update_tree`](#update_tree)\n\n\n#### `delete`\n**Delete a node** if `cascade=True` (default behaviour), children and descendants will be deleted too,\notherwise children's parent will be set to `None` (then children become roots):\n```python\nobj.delete(cascade=True)\n```\n\n#### `delete_tree`\n**Delete the whole tree** for the current node class:\n```python\ncls.delete_tree()\n```\n\n#### `get_ancestors`\nGet a **list with all ancestors** (ordered from root to parent):\n```python\nobj.get_ancestors()\n# or\nobj.ancestors\n```\n\n#### `get_ancestors_count`\nGet the **ancestors count**:\n```python\nobj.get_ancestors_count()\n# or\nobj.ancestors_count\n```\n\n#### `get_ancestors_pks`\nGet the **ancestors pks** list:\n```python\nobj.get_ancestors_pks()\n# or\nobj.ancestors_pks\n```\n\n#### `get_ancestors_queryset`\nGet the **ancestors queryset** (ordered from parent to root):\n```python\nobj.get_ancestors_queryset()\n```\n\n#### `get_breadcrumbs`\nGet the **breadcrumbs** to current node (included):\n```python\nobj.get_breadcrumbs(attr=None)\n# or\nobj.breadcrumbs\n```\n\n#### `get_children`\nGet a **list containing all children**:\n```python\nobj.get_children()\n# or\nobj.children\n```\n\n#### `get_children_count`\nGet the **children count**:\n```python\nobj.get_children_count()\n# or\nobj.children_count\n```\n\n#### `get_children_pks`\nGet the **children pks** list:\n```python\nobj.get_children_pks()\n# or\nobj.children_pks\n```\n\n#### `get_children_queryset`\nGet the **children queryset**:\n```python\nobj.get_children_queryset()\n```\n\n#### `get_depth`\nGet the **node depth** (how many levels of descendants):\n```python\nobj.get_depth()\n# or\nobj.depth\n```\n\n#### `get_descendants`\nGet a **list containing all descendants**:\n```python\nobj.get_descendants()\n# or\nobj.descendants\n```\n\n#### `get_descendants_count`\nGet the **descendants count**:\n```python\nobj.get_descendants_count()\n# or\nobj.descendants_count\n```\n\n#### `get_descendants_pks`\nGet the **descendants pks** list:\n```python\nobj.get_descendants_pks()\n# or\nobj.descendants_pks\n```\n\n#### `get_descendants_queryset`\nGet the **descendants queryset**:\n```python\nobj.get_descendants_queryset()\n```\n\n#### `get_descendants_tree`\nGet a **n-dimensional** `dict` representing the **model tree**:\n```python\nobj.get_descendants_tree()\n# or\nobj.descendants_tree\n```\n\n#### `get_descendants_tree_display`\nGet a **multiline** `string` representing the **model tree**:\n```python\nobj.get_descendants_tree_display()\n# or\nobj.descendants_tree_display\n```\n\n#### `get_first_child`\nGet the **first child node**:\n```python\nobj.get_first_child()\n# or\nobj.first_child\n```\n\n#### `get_index`\nGet the **node index** (index in node.parent.children list):\n```python\nobj.get_index()\n# or\nobj.index\n```\n\n#### `get_last_child`\nGet the **last child node**:\n```python\nobj.get_last_child()\n# or\nobj.last_child\n```\n\n#### `get_level`\nGet the **node level** (starting from 1):\n```python\nobj.get_level()\n# or\nobj.level\n```\n\n#### `get_order`\nGet the **order value** used for ordering:\n```python\nobj.get_order()\n# or\nobj.order\n```\n\n#### `get_parent`\nGet the **parent node**:\n```python\nobj.get_parent()\n# or\nobj.parent\n```\n\n#### `get_parent_pk`\nGet the **parent node pk**:\n```python\nobj.get_parent_pk()\n# or\nobj.parent_pk\n```\n\n#### `set_parent`\nSet the **parent node**:\n```python\nobj.set_parent(parent_obj)\n```\n\n#### `get_priority`\nGet the **node priority**:\n```python\nobj.get_priority()\n# or\nobj.priority\n```\n\n#### `set_priority`\nSet the **node priority**:\n```python\nobj.set_priority(100)\n```\n\n#### `get_root`\nGet the **root node** for the current node:\n```python\nobj.get_root()\n# or\nobj.root\n```\n\n#### `get_root_pk`\nGet the **root node pk** for the current node:\n```python\nobj.get_root_pk()\n# or\nobj.root_pk\n```\n\n#### `get_roots`\nGet a **list with all root nodes**:\n```python\ncls.get_roots()\n# or\ncls.roots\n```\n\n#### `get_roots_queryset`\nGet **root nodes queryset**:\n```python\ncls.get_roots_queryset()\n```\n\n#### `get_siblings`\nGet a **list with all the siblings**:\n```python\nobj.get_siblings()\n# or\nobj.siblings\n```\n\n#### `get_siblings_count`\nGet the **siblings count**:\n```python\nobj.get_siblings_count()\n# or\nobj.siblings_count\n```\n\n#### `get_siblings_pks`\nGet the **siblings pks** list:\n```python\nobj.get_siblings_pks()\n# or\nobj.siblings_pks\n```\n\n#### `get_siblings_queryset`\nGet the **siblings queryset**:\n```python\nobj.get_siblings_queryset()\n```\n\n#### `get_tree`\nGet a **n-dimensional** `dict` representing the **model tree**:\n```python\ncls.get_tree()\n# or\ncls.tree\n```\n\n#### `get_tree_display`\nGet a **multiline** `string` representing the **model tree**:\n```python\ncls.get_tree_display()\n# or\ncls.tree_display\n```\n\n#### `is_ancestor_of`\nReturn `True` if the current node **is ancestor** of target_obj:\n```python\nobj.is_ancestor_of(target_obj)\n```\n\n#### `is_child_of`\nReturn `True` if the current node **is child** of target_obj:\n```python\nobj.is_child_of(target_obj)\n```\n\n#### `is_descendant_of`\nReturn `True` if the current node **is descendant** of target_obj:\n```python\nobj.is_descendant_of(target_obj)\n```\n\n#### `is_first_child`\nReturn `True` if the current node is the **first child**:\n```python\nobj.is_first_child()\n```\n\n#### `is_last_child`\nReturn `True` if the current node is the **last child**:\n```python\nobj.is_last_child()\n```\n\n#### `is_leaf`\nReturn `True` if the current node is **leaf** (it has not children):\n```python\nobj.is_leaf()\n```\n\n#### `is_parent_of`\nReturn `True` if the current node **is parent** of target_obj:\n```python\nobj.is_parent_of(target_obj)\n```\n\n#### `is_root`\nReturn `True` if the current node **is root**:\n```python\nobj.is_root()\n```\n\n#### `is_root_of`\nReturn `True` if the current node **is root** of target_obj:\n```python\nobj.is_root_of(target_obj)\n```\n\n#### `is_sibling_of`\nReturn `True` if the current node **is sibling** of target_obj:\n```python\nobj.is_sibling_of(target_obj)\n```\n\n#### `update_tree`\n**Update tree** manually, useful after **bulk updates**:\n```python\ncls.update_tree()\n```\n\n### Bulk Operations\n\nTo perform bulk operations it is recommended to turn off signals, then triggering the tree update at the end:\n\n```python\nfrom treenode.signals import no_signals\n\nwith no_signals():\n    # execute custom bulk operations\n    pass\n\n# trigger tree update only once\nYourModel.update_tree()\n```\n\n## FAQ\n\n### Custom tree serialization\n> How can I serialize a tree using a custom data structure?\n\nThis has been discussed [here](https://github.com/fabiocaccamo/django-treenode/discussions/89#discussioncomment-5521654).\n\n## Testing\n```bash\n# clone repository\ngit clone https://github.com/fabiocaccamo/django-treenode.git && cd django-treenode\n\n# create virtualenv and activate it\npython -m venv venv && . venv/bin/activate\n\n# upgrade pip\npython -m pip install --upgrade pip\n\n# install requirements\npip install -r requirements.txt -r requirements-test.txt\n\n# install pre-commit to run formatters and linters\npre-commit install --install-hooks\n\n# run tests\ntox\n# or\npython runtests.py\n# or\npython -m django test --settings \"tests.settings\"\n```\n\n## License\nReleased under [MIT License](LICENSE.txt).\n\n---\n\n## Supporting\n\n- :star: Star this project on [GitHub](https://github.com/fabiocaccamo/django-treenode)\n- :octocat: Follow me on [GitHub](https://github.com/fabiocaccamo)\n- :blue_heart: Follow me on [Twitter](https://twitter.com/fabiocaccamo)\n- :moneybag: Sponsor me on [Github](https://github.com/sponsors/fabiocaccamo)\n\n## See also\n\n- [`django-admin-interface`](https://github.com/fabiocaccamo/django-admin-interface) - the default admin interface made customizable by the admin itself. popup windows replaced by modals. \ud83e\uddd9 \u26a1\n\n- [`django-colorfield`](https://github.com/fabiocaccamo/django-colorfield) - simple color field for models with a nice color-picker in the admin. \ud83c\udfa8\n\n- [`django-extra-settings`](https://github.com/fabiocaccamo/django-extra-settings) - config and manage typed extra settings using just the django admin. \u2699\ufe0f\n\n- [`django-maintenance-mode`](https://github.com/fabiocaccamo/django-maintenance-mode) - shows a 503 error page when maintenance-mode is on. \ud83d\udea7 \ud83d\udee0\ufe0f\n\n- [`django-redirects`](https://github.com/fabiocaccamo/django-redirects) - redirects with full control. \u21aa\ufe0f\n\n- [`python-benedict`](https://github.com/fabiocaccamo/python-benedict) - dict subclass with keylist/keypath support, I/O shortcuts (base64, csv, json, pickle, plist, query-string, toml, xml, yaml) and many utilities. \ud83d\udcd8\n\n- [`python-codicefiscale`](https://github.com/fabiocaccamo/python-codicefiscale) - encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. \ud83c\uddee\ud83c\uddf9 \ud83d\udcb3\n\n- [`python-fontbro`](https://github.com/fabiocaccamo/python-fontbro) - friendly font operations. \ud83e\udde2\n\n- [`python-fsutil`](https://github.com/fabiocaccamo/python-fsutil) - file-system utilities for lazy devs. \ud83e\udddf\u200d\u2642\ufe0f\n\n[treenode-admin-display-mode-accordion]: https://user-images.githubusercontent.com/1035294/54942407-5040ec00-4f2f-11e9-873b-d0b3b521f534.png\n[treenode-admin-display-mode-breadcrumbs]: https://user-images.githubusercontent.com/1035294/54942410-50d98280-4f2f-11e9-8a8b-a1ac6208398a.png\n[treenode-admin-display-mode-indentation]: https://user-images.githubusercontent.com/1035294/54942411-50d98280-4f2f-11e9-9daf-d8339dd7a159.png\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2018 Fabio Caccamo  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
    "summary": "probably the best abstract model/admin for your tree based stuff.",
    "version": "0.22.0",
    "project_urls": {
        "Documentation": "https://github.com/fabiocaccamo/django-treenode#readme",
        "Download": "https://github.com/fabiocaccamo/django-treenode/releases",
        "Funding": "https://github.com/sponsors/fabiocaccamo/",
        "Homepage": "https://github.com/fabiocaccamo/django-treenode",
        "Issues": "https://github.com/fabiocaccamo/django-treenode/issues",
        "Twitter": "https://twitter.com/fabiocaccamo"
    },
    "split_keywords": [
        "python",
        "django",
        "trees",
        "tree",
        "nodes",
        "node",
        "categories",
        "category",
        "ancestors",
        "parents",
        "children",
        "descendants",
        "siblings",
        "abstract",
        "model"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c2743aa76c5cc871d7d821edd03ff53dfd4d000dd99f00450f5ee93cedc3b040",
                "md5": "ed047f937eaede26c5633072d1c0b61d",
                "sha256": "e44d9934e38b5e007cc2469c92f2cd267ce5fc22d4fd960a7e35c4198a551f1f"
            },
            "downloads": -1,
            "filename": "django_treenode-0.22.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ed047f937eaede26c5633072d1c0b61d",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 21544,
            "upload_time": "2024-01-31T13:37:28",
            "upload_time_iso_8601": "2024-01-31T13:37:28.907652Z",
            "url": "https://files.pythonhosted.org/packages/c2/74/3aa76c5cc871d7d821edd03ff53dfd4d000dd99f00450f5ee93cedc3b040/django_treenode-0.22.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ff4bab32e40d6bc68373a7fb058f9b2365bbc7e0811bc5337a92d8aa5d9575d9",
                "md5": "ab2c873560c60f0e6a6d0cb930205130",
                "sha256": "61f9337a9861433e9dbd55a87088299a968a51e462555f051958b659e55b6a41"
            },
            "downloads": -1,
            "filename": "django-treenode-0.22.0.tar.gz",
            "has_sig": false,
            "md5_digest": "ab2c873560c60f0e6a6d0cb930205130",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 25822,
            "upload_time": "2024-01-31T13:37:31",
            "upload_time_iso_8601": "2024-01-31T13:37:31.073700Z",
            "url": "https://files.pythonhosted.org/packages/ff/4b/ab32e40d6bc68373a7fb058f9b2365bbc7e0811bc5337a92d8aa5d9575d9/django-treenode-0.22.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-01-31 13:37:31",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "fabiocaccamo",
    "github_project": "django-treenode#readme",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "django",
            "specs": [
                [
                    ">=",
                    "2.2"
                ]
            ]
        }
    ],
    "tox": true,
    "lcname": "django-treenode"
}
        
Elapsed time: 0.18513s