Hide keyboard shortcuts

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

2 

3# Imports 

4 

5from django.db import models 

6from django.utils.translation import ugettext_lazy as _ 

7from .utils import get_ancestors, get_descendants 

8 

9# Exports 

10 

11__all__ = ( 

12 "ParentTreeModel", 

13) 

14 

15# Models 

16 

17 

18class ParentTreeModel(models.Model): 

19 """A simple implementation of tree-like functionality using only a ``parent`` field that refers to ``self``. 

20 

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: 

23 

24 - `MPTT`_ 

25 - `Treebeard`_ 

26 

27 .. _MPTT: https://django-mptt.readthedocs.io/en/latest/index.html 

28 .. _Treebeard: http://django-treebeard.readthedocs.io/en/latest/index.html 

29 

30 """ 

31 

32 cached_level = models.PositiveSmallIntegerField( 

33 _("level"), 

34 blank=True, 

35 null=True 

36 ) 

37 

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 ) 

46 

47 class Meta: 

48 abstract = True 

49 

50 def get_ancestors(self): 

51 """Get the parent, grandparent, etc. for the current record. 

52 

53 :rtype: list 

54 

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. 

58 

59 """ 

60 return get_ancestors(self) 

61 

62 def get_children(self): 

63 """Get the child records that are directly connected to the current record. 

64 

65 :rtype: django.db.models.QuerySet 

66 

67 """ 

68 # noinspection PyUnresolvedReferences 

69 return self.children.all() 

70 

71 def get_descendants(self): 

72 """Get all of the descendants (children, grandchildren, etc.) for this record. 

73 

74 :rtype: list 

75 

76 """ 

77 return get_descendants(self) 

78 

79 def get_level(self, cached=True): 

80 """Get the level of the record within the hierarchy. 

81 

82 :param cached: Indicates whether cache should be used. 

83 :type cached: bool 

84 

85 :rtype: int 

86 

87 """ 

88 if cached and self.cached_level: 

89 return self.cached_level 

90 

91 if self.has_parent(): 

92 # noinspection PyUnresolvedReferences 

93 return self.parent.get_level() + 1 

94 

95 return 1 

96 

97 def get_siblings(self): 

98 """Get the records that are directly connected to the record's parent. 

99 

100 :rtype: django.db.models.QuerySet | None 

101 

102 """ 

103 if not self.has_parent(): 

104 return None 

105 

106 # noinspection PyUnresolvedReferences 

107 return self.parent.children.exclude(pk=self.pk) 

108 

109 def has_children(self): 

110 """Indicates whether the record has child records associated with it. 

111 

112 :rtype: bool 

113 

114 """ 

115 # noinspection PyUnresolvedReferences 

116 return self.children.exists() 

117 

118 def has_parent(self): 

119 """Indicates whether this record has a parent record. 

120 

121 :rtype: bool 

122 

123 """ 

124 # noinspection PyUnresolvedReferences 

125 return self.parent_id is not None 

126 

127 def has_siblings(self): 

128 """Indicates whether this record's parent has other child records. 

129 

130 :rtype: bool 

131 

132 """ 

133 if not self.has_parent(): 

134 return False 

135 

136 # noinspection PyUnresolvedReferences 

137 return self.parent.children.exclude(pk=self.pk).count() > 1 

138 

139 @property 

140 def level(self): 

141 """An alias of ``get_level()``.""" 

142 return self.get_level() 

143 

144 def save(self, *args, **kwargs): 

145 """Automatically save ``cached_level``.""" 

146 self.cached_level = self.get_level(cached=False) 

147 

148 super().save(*args, **kwargs)