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# Imports
3from django.core.exceptions import ImproperlyConfigured
4from .compat import ANYTREE_INSTALLED, Node, RenderTreeGraph
6# Exports
8__all__ = (
9 "get_ancestors",
10 "get_descendants",
11 "Diagram",
12)
14# Functions
17def get_ancestors(record, ancestors=None):
18 """Get the ancestors of a given model instance.
20 :param record: The model instance.
22 :param ancestors: The existing list of ancestors. Used when overloading.
23 :type ancestors: list
25 :rtype: list
27 """
28 if ancestors is None:
29 ancestors = list()
31 if record.has_parent():
32 ancestors.append(record.parent)
34 ancestors = get_ancestors(record.parent, ancestors)
36 return ancestors
39def get_descendants(parent, descendants=None):
40 """Get the descendants of a given model instance.
42 :param parent: The model instance.
44 :param descendants: The existing list of ancestors. Used when overloading.
45 :type descendants: list
47 :rtype: list
49 """
50 if descendants is None:
51 descendants = list()
53 for c in parent.children.all():
54 descendants.append(c)
55 if c.has_children():
56 descendants = get_descendants(c, descendants)
58 return descendants
61# Classes
64class Diagram(object):
65 """Utility for graphing a parent-tree model instance."""
67 def __init__(self, record):
68 """Initialize the graph.
70 :param record: The model instance.
72 """
73 if not ANYTREE_INSTALLED:
74 raise ImproperlyConfigured("Parent-tree graphing requires anytree to be installed.")
76 self.record = record
78 @staticmethod
79 def get_color(instance):
80 """Get the color to use for a record.
82 :param instance: The model instance.
84 :rtype: str
86 """
87 try:
88 return instance.color
89 except AttributeError:
90 pass
92 try:
93 return instance.get_color()
94 except AttributeError:
95 pass
97 return "#ffffff"
99 @staticmethod
100 def get_display_name(instance):
101 """Get the human-friendly name of a record.
103 :param instance: The model instance.
105 :rtype: str
107 """
108 try:
109 return instance.get_display_name()
110 except AttributeError:
111 return str(instance)
113 @staticmethod
114 def get_node_options(node):
115 """Get the options for graphing the given node. See ``to_graph()``.
117 :param node: The node for which options are to be provided.
118 :type node: Node
120 :rtype: str
122 """
123 options = list()
124 options.append("shape=box")
125 options.append("style=filled")
127 if node.color:
128 options.append('fillcolor="%s"' % node.color)
129 else:
130 options.append('fillcolor=aliceblue')
132 return ";".join(options)
134 def get_structure(self):
135 """Get the structure of the record and its children.
137 :rtype: Node
139 """
140 color = self.get_color(self.record)
141 display_name = self.get_display_name(self.record)
143 # noinspection PyCallingNonCallable
144 root_node = Node(display_name, color=color, level=self.record.level, pk=self.record.pk)
146 self._get_sub_structure(self.record, root_node)
148 return root_node
150 def to_graph(self):
151 """Get the record as a graph ready for rendering.
153 :rtype: RenderTreeGraph
155 See also: ``get_structure()``.
157 """
158 root = self.get_structure()
160 # noinspection PyCallingNonCallable
161 return RenderTreeGraph(root, nodeattrfunc=self.get_node_options)
163 def _get_sub_structure(self, instance, parent_node):
164 """Get the sub-structure of a given record.
166 :param instance: The model instance.
168 :param parent_node: The node to which sub-nodes are added.
169 :type parent_node: Node
171 """
172 for child in instance.children.all():
174 color = self.get_color(child)
175 display_name = self.get_display_name(instance)
177 # noinspection PyCallingNonCallable
178 child_node = Node(display_name, parent=parent_node, color=color, level=child.level, pk=child.pk)
180 if child.children.exists():
181 self._get_sub_structure(child, child_node)