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
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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.