|  | 
| 6 | 6 | [](https://pypi.python.org/pypi/django-hierarchical-models/) | 
| 7 | 7 | [](https://pypi.python.org/pypi/django-hierarchical-models/) | 
| 8 | 8 | 
 | 
| 9 |  | -This package provides several implementations Django models which support hierarchical | 
| 10 |  | -data. Efficiently modeling hierarchical, or tree like data, in a relational database | 
| 11 |  | -can be non-trivial. The following models implement the same interface, but there have | 
| 12 |  | -different tradeoffs. | 
|  | 9 | +This package provides an abstract Django model which supports hierarchical data. The | 
|  | 10 | +implementation is an adjacency list, which is rather naive, but actually has higher | 
|  | 11 | +performance in this scenario than other implementations such as path enumeration or | 
|  | 12 | +nested sets because those implementations store more data with each instance which must | 
|  | 13 | +be updated before almost every operation, effectively doubling (or more) database | 
|  | 14 | +queries and killing performance. The performance of this implementation actually holds | 
|  | 15 | +up pretty well at large numbers of instances. | 
| 13 | 16 | 
 | 
| 14 |  | -`models.HierarchicalModelInterface` | 
|  | 17 | +## Usage | 
| 15 | 18 | 
 | 
| 16 |  | -This abstract model defines the shared functionality of all hierarchical models. Some | 
| 17 |  | -models may implement additional methods which are cheap for their implementation. | 
|  | 19 | +```python | 
|  | 20 | +from django.db import models | 
|  | 21 | +from django_hierarchical_models.models import HierarchicalModel | 
| 18 | 22 | 
 | 
| 19 |  | -`models.AdjacencyListModel` | 
|  | 23 | +class MyModel(HierarchicalModel): | 
|  | 24 | +    name = models.CharField(max_length=100) | 
| 20 | 25 | 
 | 
| 21 |  | -This is the most trivial implementation, using a single `_parent` foreign key field. | 
| 22 |  | -Edits are efficient in this model, but queries for children/parents can be very | 
| 23 |  | -expensive. | 
|  | 26 | +... | 
| 24 | 27 | 
 | 
| 25 |  | -`models.PathEnumerationModel` | 
|  | 28 | +child = MyModel.objects.create(name="Betty") | 
|  | 29 | +child.parent()  # None | 
| 26 | 30 | 
 | 
| 27 |  | -This model uses a `_ancestors` json field to store the path to its root. This model | 
| 28 |  | -has middle ground efficiency for edits and queries. **NOTE:** This model requires | 
| 29 |  | -database features that are not available in Oracle or SQLite backends. An exception | 
| 30 |  | -will be raised if you attempt to use this model with an unsupported backend. | 
|  | 31 | +parent = MyModel.objects.create(name="Simon") | 
|  | 32 | +child.set_parent(parent) | 
|  | 33 | +child.parent()  # <MyModel: "Simon"> | 
| 31 | 34 | 
 | 
| 32 |  | -`models.NestedSetModel` | 
|  | 35 | +child.root()  # <MyModel: "Simon"> | 
|  | 36 | +parent.root()  # <MyModel: "Simon"> | 
| 33 | 37 | 
 | 
| 34 |  | -This model uses `_left` and `_right` integer fields to determine which instances it is | 
| 35 |  | -parent/child to. Queries can be very efficient in this model, but edits can be very | 
| 36 |  | -expensive, possibly even requiring updates to `_left` and `_right` fields of ever model | 
| 37 |  | -instance for a single edit. | 
|  | 38 | +parent.direct_children()  # [<MyModel: "Betty">] | 
| 38 | 39 | 
 | 
| 39 |  | -`models.Node` | 
|  | 40 | +child.is_child_of(parent)  # True | 
|  | 41 | +parent.is_child_of(child)  # False | 
|  | 42 | +``` | 
| 40 | 43 | 
 | 
| 41 |  | -Calls to `HierarchicalModel.children()` return this type, which has `instance` and | 
| 42 |  | -`children` members, with the children being additional instances of `Node`. | 
|  | 44 | +## Refreshing from database | 
| 43 | 45 | 
 | 
| 44 |  | -#### Benchmarks | 
|  | 46 | +External changes to an instance's parent are not automatically reflected in the | 
|  | 47 | +instance. This leads to the following behavior: | 
| 45 | 48 | 
 | 
| 46 |  | -These benchmarks are to illustrate the relative performance of the different models. As | 
| 47 |  | -of now they are kind of whacked out. These tests were run with Postgres. | 
|  | 49 | +```python | 
|  | 50 | +instance_1 = MyModel.objects.create(name="Betty") | 
|  | 51 | +instance_2 = MyModel.objects.create(parent=instance_1, name="Simon") | 
|  | 52 | +insstance_2.parent()  # <MyModel: "Betty"> | 
| 48 | 53 | 
 | 
| 49 |  | -| Model            | Query Ancestors | Query Parent | Query Children | Query Direct Children | Create | Create Child | Delete | Delete Parent | Add Child | Remove Child | Set Parent | Set Parent Unchecked* | | 
| 50 |  | -|------------------|-----------------|--------------|----------------|-----------------------|--------|--------------|--------|---------------|-----------|--------------|------------|-----------------------| | 
| 51 |  | -| Adjacency List   | 9.56            | 2.76         | 5.17           | 0.96                  | 0.98   | 0.33         | 0.72   | 0.95          | 0.69      | 1.64         | 133.60     | 0.93                  | | 
| 52 |  | -| Path Enumeration | 9.17            | 2.67         | 29.71          | 0.84                  | 0.99   | 0.32         | 0.75   | 1.26          | 0.94      | 1.79         | 2.98       |                       | | 
| 53 |  | -| Nested Set       | 169.86          | 118.97       | 211.75         | 107.30                | 3.31   | 59.19        | 113.79 | 175.11        | 71.13     | 293.61       | 572.47     |                       | | 
|  | 54 | +instance_1.delete() | 
| 54 | 55 | 
 | 
| 55 |  | -\* function only available on Adjacency List Model | 
|  | 56 | +instance_2.parent()  # <MyModel: "Betty"> | 
|  | 57 | + | 
|  | 58 | +instance_2.refresh_from_db() | 
|  | 59 | + | 
|  | 60 | +instance_2.parent()  # None | 
|  | 61 | +``` | 
|  | 62 | + | 
|  | 63 | +```python | 
|  | 64 | +instance_1 = MyModel.objects.create(name="Betty") | 
|  | 65 | +instance_2 = MyModel.objects.create(parent=instance_1, name="Simon") | 
|  | 66 | +instance_3 = MyModel.objects.create(parent=instance_2, name="Finn") | 
|  | 67 | +instance_3_copy = MyModel.objects.get(pk=instance_3.pk) | 
|  | 68 | + | 
|  | 69 | +instance_1.root()  # <MyModel: "Betty"> | 
|  | 70 | +instance_2.root()  # <MyModel: "Betty"> | 
|  | 71 | +instance_3.root()  # <MyModel: "Betty"> | 
|  | 72 | +instance_3_copy.root()  # <MyModel: "Betty"> | 
|  | 73 | + | 
|  | 74 | +instance_2.set_parent(None) | 
|  | 75 | + | 
|  | 76 | +instance_1.root()  # <MyModel: "Betty"> | 
|  | 77 | +instance_2.root()  # <MyModel: "Simon"> | 
|  | 78 | +instance_3.root()  # <MyModel: "Simon"> | 
|  | 79 | +instance_3_copy.root()  # <MyModel: "Betty"> | 
|  | 80 | + | 
|  | 81 | +instance_3_copy.refresh_from_db() | 
|  | 82 | + | 
|  | 83 | +instance_1.root()  # <MyModel: "Betty"> | 
|  | 84 | +instance_2.root()  # <MyModel: "Simon"> | 
|  | 85 | +instance_3.root()  # <MyModel: "Simon"> | 
|  | 86 | +instance_3_copy.root()  # <MyModel: "Simon"> | 
|  | 87 | +``` | 
|  | 88 | + | 
|  | 89 | +Moral of the story, if your instance's parent might have been edited/deleted, | 
|  | 90 | +you will want to refresh your instance for that change to be reflected.   | 
|  | 91 | + | 
|  | 92 | +## Benchmarks | 
|  | 93 | + | 
|  | 94 | +The following benchmarks demonstrate that the query performance of the model stays the | 
|  | 95 | +same from 10,000 to 1,000,000 models. These tests were done with Postgres. The results | 
|  | 96 | +are in the form `total time (s) / per instance (ms)`. Eventually the query performance | 
|  | 97 | +of this model should scale down with the total number of instances in the database, | 
|  | 98 | +but it appears up to these scales those effects are insignificant compared to other | 
|  | 99 | +overhead. | 
|  | 100 | + | 
|  | 101 | +| n         | Chance Child | Query Parent  | Query Root    | Is Child Of   | Query Ancestors | Query Direct Children | Query Children | | 
|  | 102 | +|-----------|--------------|---------------|---------------|---------------|-----------------|-----------------------|----------------| | 
|  | 103 | +| 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   | | 
|  | 104 | +| 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   | | 
|  | 105 | +| 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  | | 
|  | 106 | +| 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  | | 
|  | 107 | +| 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 | | 
|  | 108 | +| 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 | | 
0 commit comments