django-hierarchical-models


Namedjango-hierarchical-models JSON
Version 2.1.0 PyPI version JSON
download
home_pagehttps://github.com/rcxwhiz/django-hierarchical-models
SummaryAdds hierarchical models to Django
upload_time2024-04-29 00:43:08
maintainerNone
docs_urlNone
authorJosh Bedwell
requires_python<4.0,>=3.10
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # django-hierarchical-models

[![Tests](https://github.com/rcxwhiz/django-hierarchical-models/actions/workflows/test.yml/badge.svg)](https://github.com/rcxwhiz/django-hierarchical-models/actions/workflows/)
[![Coverage](https://codecov.io/gh/rcxwhiz/django-hierarchical-models/branch/main/graph/badge.svg)](https://codecov.io/gh/rcxwhiz/django-hierarchical-models/)
[![PyPi](https://img.shields.io/pypi/v/django-hierarchical-models.svg)](https://pypi.python.org/pypi/django-hierarchical-models/)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/django-hierarchical-models.svg)](https://pypi.python.org/pypi/django-hierarchical-models/)
[![Supported Django versions](https://img.shields.io/pypi/djversions/django-hierarchical-models.svg)](https://pypi.python.org/pypi/django-hierarchical-models/)

This package provides an abstract Django model which supports hierarchical data. The
implementation is an adjacency list, which is rather naive, but actually has higher
performance in this scenario than other implementations such as path enumeration or
nested sets because those implementations store more data with each instance which must
be updated before almost every operation, effectively doubling (or more) database
queries and killing performance. The performance of this implementation actually holds
up pretty well at large numbers of instances.

## Usage

```python
from django.db import models
from django_hierarchical_models.models import HierarchicalModel

class MyModel(HierarchicalModel):
    name = models.CharField(max_length=100)

...

child = MyModel.objects.create(name="Betty")
child.parent  # None

parent = MyModel.objects.create(name="Simon")
# checks for pesky cycles
child.set_parent(parent)
# alternative
# child.parent = parent
child.parent  # <MyModel: "Simon">

child.root()  # <MyModel: "Simon">
parent.root()  # <MyModel: "Simon">

parent.direct_children()  # [<MyModel: "Betty">]

child.is_child_of(parent)  # True
parent.is_child_of(child)  # False
```

## parent = vs .set_parent()

`parent` is a `ForeignKeyField` which may be directly accessed or set. The
`.set_parent()` method checks to see if the operation would create a cycle, which can
be bad for some of the other instance methods. The `.set_parent()` method is slower
because it must determine if a cycle would be formed. `.set_parent()` makes a call to
`.save(update_fields=("parent",))`, so it is not necessary to call `.save()` after
updating the parent this way.

## Refreshing from database

The following is expected behavior:

```python
instance_1 = MyModel.objects.create(name="Betty")
instance_2 = MyModel.objects.create(parent=instance_1, name="Simon")
instance_2.parent  # <MyModel: "Betty">

instance_1.delete()

instance_2.parent  # <MyModel: "Betty">

instance_2.refresh_from_db()

instance_2.parent  # None
```

```python
instance_1 = MyModel.objects.create(name="Betty")
instance_2 = MyModel.objects.create(parent=instance_1, name="Simon")
instance_3 = MyModel.objects.create(parent=instance_2, name="Finn")
instance_3_copy = MyModel.objects.get(pk=instance_3.pk)

instance_1.root()  # <MyModel: "Betty">
instance_2.root()  # <MyModel: "Betty">
instance_3.root()  # <MyModel: "Betty">
instance_3_copy.root()  # <MyModel: "Betty">

instance_2.set_parent(None)

instance_1.root()  # <MyModel: "Betty">
instance_2.root()  # <MyModel: "Simon">
instance_3.root()  # <MyModel: "Simon">
instance_3_copy.root()  # <MyModel: "Betty">

instance_3_copy.refresh_from_db()

instance_1.root()  # <MyModel: "Betty">
instance_2.root()  # <MyModel: "Simon">
instance_3.root()  # <MyModel: "Simon">
instance_3_copy.root()  # <MyModel: "Simon">
```

Moral of the story, if your instance's parent might have been edited/deleted, you will
want to refresh your instance for that change to be reflected.  

## Benchmarks

The following benchmarks demonstrate that the query performance of the model stays the
same from 10,000 to 1,000,000 models. These tests were done with Postgres. The results
are in the form `total time (s) / per instance (ms)`. Eventually the query performance
of this model should scale down with the total number of instances in the database,
but it appears up to these scales those effects are insignificant compared to other
overhead.

| n         | Chance Child | Query Parent  | Query Root    | Is Child Of   | Query Ancestors | Query Direct Children | Query Children |
|-----------|--------------|---------------|---------------|---------------|-----------------|-----------------------|----------------|
| 10,000    | 50%          | 0.29 / 0.029  | 0.27 / 0.027  | 0.27 / 0.027  | 0.29 / 0.029    | 0.78 / 0.078          | 3.85 / 0.385   |
| 10,000    | 90%          | 0.30 / 0.030  | 0.39 / 0.039  | 0.31 / 0.031  | 0.30 / 0.030    | 0.87 / 0.087          | 5.07 / 0.507   |
| 100,000   | 50%          | 3.46 / 0.035  | 3.12 / 0.031  | 3.55 / 0.036  | 3.09 / 0.031    | 8.24 / 0.082          | 37.89 / 0.380  |
| 100,000   | 90%          | 4.10 / 0.041  | 3.48 / 0.035  | 3.88 / 0.039  | 3.55 / 0.036    | 8.89 / 0.089          | 48.30 / 0.483  |
| 1,000,000 | 50%          | 32.39 / 0.032 | 34.53 / 0.035 | 35.41 / 0.035 | 32.16 / 0.032   | 86.05 / 0.086         | 385.62 / 0.386 |
| 1,000,000 | 90%          | 34.87 / 0.035 | 38.59 / 0.039 | 38.93 / 0.039 | 36.51 / 0.037   | 87.49 / 0.087         | 490.65 / 0.491 |

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/rcxwhiz/django-hierarchical-models",
    "name": "django-hierarchical-models",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.10",
    "maintainer_email": null,
    "keywords": null,
    "author": "Josh Bedwell",
    "author_email": "rcxwhiz@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/83/cf/aa3f0761935d6b6cc44b833a0a7970f17ba093cd6a051cdf9fe478d4d420/django_hierarchical_models-2.1.0.tar.gz",
    "platform": null,
    "description": "# django-hierarchical-models\n\n[![Tests](https://github.com/rcxwhiz/django-hierarchical-models/actions/workflows/test.yml/badge.svg)](https://github.com/rcxwhiz/django-hierarchical-models/actions/workflows/)\n[![Coverage](https://codecov.io/gh/rcxwhiz/django-hierarchical-models/branch/main/graph/badge.svg)](https://codecov.io/gh/rcxwhiz/django-hierarchical-models/)\n[![PyPi](https://img.shields.io/pypi/v/django-hierarchical-models.svg)](https://pypi.python.org/pypi/django-hierarchical-models/)\n[![Supported Python versions](https://img.shields.io/pypi/pyversions/django-hierarchical-models.svg)](https://pypi.python.org/pypi/django-hierarchical-models/)\n[![Supported Django versions](https://img.shields.io/pypi/djversions/django-hierarchical-models.svg)](https://pypi.python.org/pypi/django-hierarchical-models/)\n\nThis package provides an abstract Django model which supports hierarchical data. The\nimplementation is an adjacency list, which is rather naive, but actually has higher\nperformance in this scenario than other implementations such as path enumeration or\nnested sets because those implementations store more data with each instance which must\nbe updated before almost every operation, effectively doubling (or more) database\nqueries and killing performance. The performance of this implementation actually holds\nup pretty well at large numbers of instances.\n\n## Usage\n\n```python\nfrom django.db import models\nfrom django_hierarchical_models.models import HierarchicalModel\n\nclass MyModel(HierarchicalModel):\n    name = models.CharField(max_length=100)\n\n...\n\nchild = MyModel.objects.create(name=\"Betty\")\nchild.parent  # None\n\nparent = MyModel.objects.create(name=\"Simon\")\n# checks for pesky cycles\nchild.set_parent(parent)\n# alternative\n# child.parent = parent\nchild.parent  # <MyModel: \"Simon\">\n\nchild.root()  # <MyModel: \"Simon\">\nparent.root()  # <MyModel: \"Simon\">\n\nparent.direct_children()  # [<MyModel: \"Betty\">]\n\nchild.is_child_of(parent)  # True\nparent.is_child_of(child)  # False\n```\n\n## parent = vs .set_parent()\n\n`parent` is a `ForeignKeyField` which may be directly accessed or set. The\n`.set_parent()` method checks to see if the operation would create a cycle, which can\nbe bad for some of the other instance methods. The `.set_parent()` method is slower\nbecause it must determine if a cycle would be formed. `.set_parent()` makes a call to\n`.save(update_fields=(\"parent\",))`, so it is not necessary to call `.save()` after\nupdating the parent this way.\n\n## Refreshing from database\n\nThe following is expected behavior:\n\n```python\ninstance_1 = MyModel.objects.create(name=\"Betty\")\ninstance_2 = MyModel.objects.create(parent=instance_1, name=\"Simon\")\ninstance_2.parent  # <MyModel: \"Betty\">\n\ninstance_1.delete()\n\ninstance_2.parent  # <MyModel: \"Betty\">\n\ninstance_2.refresh_from_db()\n\ninstance_2.parent  # None\n```\n\n```python\ninstance_1 = MyModel.objects.create(name=\"Betty\")\ninstance_2 = MyModel.objects.create(parent=instance_1, name=\"Simon\")\ninstance_3 = MyModel.objects.create(parent=instance_2, name=\"Finn\")\ninstance_3_copy = MyModel.objects.get(pk=instance_3.pk)\n\ninstance_1.root()  # <MyModel: \"Betty\">\ninstance_2.root()  # <MyModel: \"Betty\">\ninstance_3.root()  # <MyModel: \"Betty\">\ninstance_3_copy.root()  # <MyModel: \"Betty\">\n\ninstance_2.set_parent(None)\n\ninstance_1.root()  # <MyModel: \"Betty\">\ninstance_2.root()  # <MyModel: \"Simon\">\ninstance_3.root()  # <MyModel: \"Simon\">\ninstance_3_copy.root()  # <MyModel: \"Betty\">\n\ninstance_3_copy.refresh_from_db()\n\ninstance_1.root()  # <MyModel: \"Betty\">\ninstance_2.root()  # <MyModel: \"Simon\">\ninstance_3.root()  # <MyModel: \"Simon\">\ninstance_3_copy.root()  # <MyModel: \"Simon\">\n```\n\nMoral of the story, if your instance's parent might have been edited/deleted, you will\nwant to refresh your instance for that change to be reflected.  \n\n## Benchmarks\n\nThe following benchmarks demonstrate that the query performance of the model stays the\nsame from 10,000 to 1,000,000 models. These tests were done with Postgres. The results\nare in the form `total time (s) / per instance (ms)`. Eventually the query performance\nof this model should scale down with the total number of instances in the database,\nbut it appears up to these scales those effects are insignificant compared to other\noverhead.\n\n| n         | Chance Child | Query Parent  | Query Root    | Is Child Of   | Query Ancestors | Query Direct Children | Query Children |\n|-----------|--------------|---------------|---------------|---------------|-----------------|-----------------------|----------------|\n| 10,000    | 50%          | 0.29 / 0.029  | 0.27 / 0.027  | 0.27 / 0.027  | 0.29 / 0.029    | 0.78 / 0.078          | 3.85 / 0.385   |\n| 10,000    | 90%          | 0.30 / 0.030  | 0.39 / 0.039  | 0.31 / 0.031  | 0.30 / 0.030    | 0.87 / 0.087          | 5.07 / 0.507   |\n| 100,000   | 50%          | 3.46 / 0.035  | 3.12 / 0.031  | 3.55 / 0.036  | 3.09 / 0.031    | 8.24 / 0.082          | 37.89 / 0.380  |\n| 100,000   | 90%          | 4.10 / 0.041  | 3.48 / 0.035  | 3.88 / 0.039  | 3.55 / 0.036    | 8.89 / 0.089          | 48.30 / 0.483  |\n| 1,000,000 | 50%          | 32.39 / 0.032 | 34.53 / 0.035 | 35.41 / 0.035 | 32.16 / 0.032   | 86.05 / 0.086         | 385.62 / 0.386 |\n| 1,000,000 | 90%          | 34.87 / 0.035 | 38.59 / 0.039 | 38.93 / 0.039 | 36.51 / 0.037   | 87.49 / 0.087         | 490.65 / 0.491 |\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Adds hierarchical models to Django",
    "version": "2.1.0",
    "project_urls": {
        "Homepage": "https://github.com/rcxwhiz/django-hierarchical-models",
        "Repository": "https://github.com/rcxwhiz/django-hierarchical-models"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3f4cdb6db7118ca67d2ed184fb20da276734aeaf12dfb69e2fd71bfbf0041ee7",
                "md5": "825809eaa28f96ac8e49fc08cf3f45a5",
                "sha256": "48413cbff21ca11f7fa829dc3369b42a62cd3c66369f00df8628ec87816f6f3f"
            },
            "downloads": -1,
            "filename": "django_hierarchical_models-2.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "825809eaa28f96ac8e49fc08cf3f45a5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.10",
            "size": 7250,
            "upload_time": "2024-04-29T00:43:06",
            "upload_time_iso_8601": "2024-04-29T00:43:06.965068Z",
            "url": "https://files.pythonhosted.org/packages/3f/4c/db6db7118ca67d2ed184fb20da276734aeaf12dfb69e2fd71bfbf0041ee7/django_hierarchical_models-2.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "83cfaa3f0761935d6b6cc44b833a0a7970f17ba093cd6a051cdf9fe478d4d420",
                "md5": "57332e9131b71294370f40c34b42bc46",
                "sha256": "f981715690abd2b0608a9e19cb22e9974c08eb03ad8e0b44d495a5caed867944"
            },
            "downloads": -1,
            "filename": "django_hierarchical_models-2.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "57332e9131b71294370f40c34b42bc46",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.10",
            "size": 5765,
            "upload_time": "2024-04-29T00:43:08",
            "upload_time_iso_8601": "2024-04-29T00:43:08.695521Z",
            "url": "https://files.pythonhosted.org/packages/83/cf/aa3f0761935d6b6cc44b833a0a7970f17ba093cd6a051cdf9fe478d4d420/django_hierarchical_models-2.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-29 00:43:08",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "rcxwhiz",
    "github_project": "django-hierarchical-models",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "django-hierarchical-models"
}
        
Elapsed time: 0.56992s