Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
3# Imports
5from django.db import models
6from django.utils.translation import ugettext_lazy as _
7from .utils import get_ancestors, get_descendants
9# Exports
11__all__ = (
12 "ParentTreeModel",
13)
15# Models
18class ParentTreeModel(models.Model):
19 """A simple implementation of tree-like functionality using only a ``parent`` field that refers to ``self``.
21 Note that some methods will be horribly inefficient, yet effective at finding all of the records associated with the
22 current instance. If performance is a concern, then you may wish to review the following resources:
24 - `MPTT`_
25 - `Treebeard`_
27 .. _MPTT: https://django-mptt.readthedocs.io/en/latest/index.html
28 .. _Treebeard: http://django-treebeard.readthedocs.io/en/latest/index.html
30 """
32 cached_level = models.PositiveSmallIntegerField(
33 _("level"),
34 blank=True,
35 null=True
36 )
38 parent = models.ForeignKey(
39 "self",
40 blank=True,
41 null=True,
42 on_delete=models.SET_NULL,
43 related_name="children",
44 verbose_name=_("parent")
45 )
47 class Meta:
48 abstract = True
50 def get_ancestors(self):
51 """Get the parent, grandparent, etc. for the current record.
53 :rtype: list
55 .. note::
56 The list is populated from nearest ancestor to furthest ancestor. Use ``reversed()`` as needed to re-order
57 the list so that the top-most ancestor is first.
59 """
60 return get_ancestors(self)
62 def get_children(self):
63 """Get the child records that are directly connected to the current record.
65 :rtype: django.db.models.QuerySet
67 """
68 # noinspection PyUnresolvedReferences
69 return self.children.all()
71 def get_descendants(self):
72 """Get all of the descendants (children, grandchildren, etc.) for this record.
74 :rtype: list
76 """
77 return get_descendants(self)
79 def get_level(self, cached=True):
80 """Get the level of the record within the hierarchy.
82 :param cached: Indicates whether cache should be used.
83 :type cached: bool
85 :rtype: int
87 """
88 if cached and self.cached_level:
89 return self.cached_level
91 if self.has_parent():
92 # noinspection PyUnresolvedReferences
93 return self.parent.get_level() + 1
95 return 1
97 def get_siblings(self):
98 """Get the records that are directly connected to the record's parent.
100 :rtype: django.db.models.QuerySet | None
102 """
103 if not self.has_parent():
104 return None
106 # noinspection PyUnresolvedReferences
107 return self.parent.children.exclude(pk=self.pk)
109 def has_children(self):
110 """Indicates whether the record has child records associated with it.
112 :rtype: bool
114 """
115 # noinspection PyUnresolvedReferences
116 return self.children.exists()
118 def has_parent(self):
119 """Indicates whether this record has a parent record.
121 :rtype: bool
123 """
124 # noinspection PyUnresolvedReferences
125 return self.parent_id is not None
127 def has_siblings(self):
128 """Indicates whether this record's parent has other child records.
130 :rtype: bool
132 """
133 if not self.has_parent():
134 return False
136 # noinspection PyUnresolvedReferences
137 return self.parent.children.exclude(pk=self.pk).count() > 1
139 @property
140 def level(self):
141 """An alias of ``get_level()``."""
142 return self.get_level()
144 def save(self, *args, **kwargs):
145 """Automatically save ``cached_level``."""
146 self.cached_level = self.get_level(cached=False)
148 super().save(*args, **kwargs)