Skip to main content

django-hierarchical-models

Tests Coverage PyPi Supported Python versions Supported Django versions

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

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:

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
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

Metadata

Release files for django-hierarchical-models 2.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-hierarchical-models 2.1.0
File Size Uploaded Provenance Actions
django_hierarchical_models-2.1.0.tar.gz 5.8 kB Without provenance Details for django_hierarchical_models-2.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-hierarchical-models 2.1.0
File Interpreter ABI Platform Actions
django_hierarchical_models-2.1.0-py3-none-any.whl Python 3 none any Details for django_hierarchical_models-2.1.0-py3-none-any.whl

Total release size: 13.0 kB

Release files / django_hierarchical_models-2.1.0.tar.gz

Download URL django_hierarchical_models-2.1.0.tar.gz
Size 5.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f981715690abd2b0608a9e19cb22e9974c08eb03ad8e0b44d495a5caed867944
BLAKE2b-256 checksum
How to use checksums
83cfaa3f0761935d6b6cc44b833a0a7970f17ba093cd6a051cdf9fe478d4d420
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.2 CPython/3.12.1 Linux/6.5.0-1018-azure

Release files / django_hierarchical_models-2.1.0-py3-none-any.whl

Download URL django_hierarchical_models-2.1.0-py3-none-any.whl
Size 7.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
48413cbff21ca11f7fa829dc3369b42a62cd3c66369f00df8628ec87816f6f3f
BLAKE2b-256 checksum
How to use checksums
3f4cdb6db7118ca67d2ed184fb20da276734aeaf12dfb69e2fd71bfbf0041ee7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.2 CPython/3.12.1 Linux/6.5.0-1018-azure

Security for django-hierarchical-models 2.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page