11"""
2- A class to model hierarchies of objects following Directed Acyclic Graph structure.
2+ A set of model classes to model hierarchies of objects following Directed Acyclic Graph structure.
33
4- The graph traversal queries use Postgresql's recursive CTEs to fetch an entire tree
5- of related node ids in a single query. These queries also topologically sort the ids
6- by generation.
4+ The graph traversal queries use Postgresql's recursive CTEs to fetch an entire tree of related node ids in a single
5+ query. These queries also topologically sort the ids by generation.
76"""
87
98from django .apps import apps
1817
1918class NodeManager (models .Manager ):
2019 def roots (self , node = None ):
21- """Returns a Queryset of all root Nodes, or optionally, the roots of a select node"""
20+ """
21+ Returns a Queryset of all root nodes (nodes with no parents) in the Node model. If a node instance is specified,
22+ returns only the roots for that node.
23+ """
2224 if node is not None :
2325 return node .roots ()
2426 return self .filter (parents__isnull = True )
2527
2628 def leaves (self , node = None ):
27- """Returns a Queryset of all leaf Nodes, or optionally, the leaves of a select node"""
29+ """
30+ Returns a Queryset of all leaf nodes (nodes with no children) in the Node model. If a node instance is
31+ specified, returns only the leaves for that node.
32+ """
2833 if node is not None :
2934 return node .leaves ()
3035 return self .filter (children__isnull = True )
@@ -72,6 +77,7 @@ def ordered_queryset_from_pks(self, pks):
7277 return _ordered_filter (self .__class__ .objects , "pk" , pks )
7378
7479 def add_child (self , child , ** kwargs ):
80+ """Provided with a Node instance, attaches that instance as a child to the current Node instance"""
7581 kwargs .update ({"parent" : self , "child" : child })
7682
7783 disable_circular_check = kwargs .pop ("disable_circular_check" , False )
@@ -81,7 +87,10 @@ def add_child(self, child, **kwargs):
8187 return cls .save (disable_circular_check = disable_circular_check , allow_duplicate_edges = allow_duplicate_edges )
8288
8389 def remove_child (self , child , delete_node = False ):
84- """Removes the edge connecting this node to child, and optionally deletes the child node as well"""
90+ """
91+ Removes the edge connecting this node to the provided child Node instance, and optionally deletes the child
92+ node as well
93+ """
8594 if child in self .children .all ():
8695 self .children .through .objects .filter (parent = self , child = child ).delete ()
8796 if delete_node :
@@ -92,6 +101,7 @@ def remove_child(self, child, delete_node=False):
92101 child .delete ()
93102
94103 def add_parent (self , parent , * args , ** kwargs ):
104+ """Provided with a Node instance, attaches the current instance as a child to the provided Node instance"""
95105 return parent .add_child (self , ** kwargs )
96106
97107 def remove_parent (self , parent , delete_node = False ):
@@ -106,44 +116,54 @@ def remove_parent(self, parent, delete_node=False):
106116 parent .delete ()
107117
108118 def ancestors_raw (self , ** kwargs ):
119+ """Returns a raw QuerySet of all nodes in connected paths in a rootward direction"""
109120 return AncestorQuery (instance = self , ** kwargs ).raw_queryset ()
110121
111122 def ancestors (self , ** kwargs ):
123+ """Returns a QuerySet of all nodes in connected paths in a rootward direction"""
112124 pks = [item .pk for item in self .ancestors_raw (** kwargs )]
113125 return self .ordered_queryset_from_pks (pks )
114126
115127 def ancestors_count (self ):
128+ """Returns an integer number representing the total number of ancestor nodes"""
116129 return self .ancestors ().count ()
117130
118131 def self_and_ancestors (self , ** kwargs ):
132+ """Returns a QuerySet of all nodes in connected paths in a rootward direction, prepending with self"""
119133 pks = [self .pk ] + [item .pk for item in self .ancestors_raw (** kwargs )][::- 1 ]
120134 return self .ordered_queryset_from_pks (pks )
121135
122136 def ancestors_and_self (self , ** kwargs ):
137+ """Returns a QuerySet of all nodes in connected paths in a rootward direction, appending with self"""
123138 pks = [item .pk for item in self .ancestors_raw (** kwargs )] + [self .pk ]
124139 return self .ordered_queryset_from_pks (pks )
125140
126141 def descendants_raw (self , ** kwargs ):
142+ """Returns a raw QuerySet of all nodes in connected paths in a leafward direction"""
127143 return DescendantQuery (instance = self , ** kwargs ).raw_queryset ()
128144
129145 def descendants (self , ** kwargs ):
146+ """Returns a QuerySet of all nodes in connected paths in a leafward direction"""
130147 pks = [item .pk for item in self .descendants_raw (** kwargs )]
131148 return self .ordered_queryset_from_pks (pks )
132149
133150 def descendants_count (self ):
151+ """Returns an integer number representing the total number of descendant nodes"""
134152 return self .descendants ().count ()
135153
136154 def self_and_descendants (self , ** kwargs ):
155+ """Returns a QuerySet of all nodes in connected paths in a leafward direction, prepending with self"""
137156 pks = [self .pk ] + [item .pk for item in self .descendants_raw (** kwargs )]
138157 return self .ordered_queryset_from_pks (pks )
139158
140159 def descendants_and_self (self , ** kwargs ):
160+ """Returns a QuerySet of all nodes in connected paths in a leafward direction, appending with self"""
141161 pks = [item .pk for item in self .descendants_raw (** kwargs )] + [self .pk ]
142162 return self .ordered_queryset_from_pks (pks )
143163
144164 def clan (self , ** kwargs ):
145165 """
146- Returns a queryset with all ancestors, self, and all descendants
166+ Returns a QuerySet with all ancestors nodes , self, and all descendant nodes
147167 """
148168 pks = (
149169 [item .pk for item in self .ancestors_raw (** kwargs )]
@@ -153,22 +173,23 @@ def clan(self, **kwargs):
153173 return self .ordered_queryset_from_pks (pks )
154174
155175 def clan_count (self ):
176+ """Returns an integer number representing the total number of clan nodes"""
156177 return self .clan ().count ()
157178
158179 def siblings (self ):
159- # Returns all nodes that share a parent with this node
180+ """ Returns a QuerySet of all nodes that share a parent with this node, excluding self"""
160181 return self .siblings_with_self ().exclude (pk = self .pk )
161182
162183 def siblings_count (self ):
163- # Returns count of all nodes that share a parent with this node
184+ """ Returns count of all nodes that share a parent with this node"""
164185 return self .siblings ().count ()
165186
166187 def siblings_with_self (self ):
167- # Returns all nodes that share a parent with this node and self
188+ """ Returns a QuerySet of all nodes that share a parent with this node and self"""
168189 return self .__class__ .objects .filter (parents__in = self .parents .all ()).distinct ()
169190
170191 def partners (self ):
171- # Returns all nodes that share a child with this node
192+ """ Returns a QuerySet of all nodes that share a child with this node"""
172193 return self .partners_with_self ().exclude (pk = self .pk )
173194
174195 def partners_count (self ):
@@ -200,12 +221,21 @@ def path_raw(self, ending_node, directional=True, **kwargs):
200221 return path
201222
202223 def path_exists (self , ending_node , ** kwargs ):
224+ """
225+ Given an ending Node instance, returns a boolean value determining whether there is a path from the current
226+ Node instance to the ending Node instance
227+ """
203228 try :
204229 return len (list (self .path_raw (ending_node , ** kwargs ))) >= 1
205230 except NodeNotReachableException :
206231 return False
207232
208233 def path (self , ending_node , ** kwargs ):
234+ """
235+ Returns a QuerySet of the shortest path from self to ending node, optionally in either direction.
236+ The resulting Queryset is sorted from root-side, toward leaf-side, regardless of the relative position of
237+ starting and ending nodes.
238+ """
209239 pks = [item .pk for item in self .path_raw (ending_node , ** kwargs )]
210240 return self .ordered_queryset_from_pks (pks )
211241
@@ -220,68 +250,85 @@ def distance(self, ending_node, **kwargs):
220250
221251 def is_root (self ):
222252 """
223- Check if has children and not ancestors
253+ Returns True if the current Node instance has children, but no parents
224254 """
225255 return bool (self .children .exists () and not self .parents .exists ())
226256
227257 def is_leaf (self ):
228258 """
229- Check if has ancestors and not children
259+ Returns True if the current Node instance has parents, but no children
230260 """
231261 return bool (self .parents .exists () and not self .children .exists ())
232262
233263 def is_island (self ):
234264 """
235- Check if has no ancestors nor children
265+ Returns True if the current Node instance has no parents nor children
236266 """
237267 return bool (not self .children .exists () and not self .parents .exists ())
238268
239269 def is_ancestor_of (self , ending_node , ** kwargs ):
270+ """
271+ Provided an ending_node Node instance, returns True if the current Node instance and is an ancestor of the
272+ provided Node instance
273+ """
240274 try :
241275 return len (self .path_raw (ending_node , ** kwargs )) >= 1
242276 except NodeNotReachableException :
243277 return False
244278
245279 def is_descendant_of (self , ending_node , ** kwargs ):
280+ """
281+ Provided an ending_node Node instance, returns True if the current Node instance and is a descendant of the
282+ provided Node instance
283+ """
246284 return (
247285 not self .is_ancestor_of (ending_node , ** kwargs )
248286 and len (self .path_raw (ending_node , directional = False , ** kwargs )) >= 1
249287 )
250288
251289 def is_sibling_of (self , ending_node ):
290+ """
291+ Provided an ending_node Node instance, returns True if the provided Node instance and the current Node
292+ instance share a parent Node
293+ """
252294 return ending_node in self .siblings ()
253295
254296 def is_partner_of (self , ending_node ):
297+ """
298+ Provided an ending_node Node instance, returns True if the provided Node instance and the current Node
299+ instance share a child Node
300+ """
255301 return ending_node in self .partners ()
256302
257303 def node_depth (self ):
258- # Depth from furthest root
304+ """Returns an integer representing the depth of this Node instance from furthest root"""
259305 # ToDo: Implement
260306 pass
261307
262308 def connected_graph_raw (self , ** kwargs ):
263- # Gets all nodes connected in any way to this node
309+ """Returns a raw QuerySet of all nodes connected in any way to the current Node instance"""
264310 return ConnectedGraphQuery (instance = self , ** kwargs ).raw_queryset ()
265311
266312 def connected_graph (self , ** kwargs ):
313+ """Returns a QuerySet of all nodes connected in any way to the current Node instance"""
267314 pks = [item .pk for item in self .connected_graph_raw (** kwargs )]
268315 return self .ordered_queryset_from_pks (pks )
269316
270317 def descendants_tree (self ):
271318 """
272- Returns a tree-like structure with descendants
273- # ToDo: Modify to use CTE
319+ Returns a tree-like structure with descendants for the current Node
274320 """
321+ # ToDo: Modify to use CTE
275322 tree = {}
276323 for child in self .children .all ():
277324 tree [child ] = child .descendants_tree ()
278325 return tree
279326
280327 def ancestors_tree (self ):
281328 """
282- Returns a tree-like structure with ancestors
283- # ToDo: Modify to use CTE
329+ Returns a tree-like structure with ancestors for the current Node
284330 """
331+ # ToDo: Modify to use CTE
285332 tree = {}
286333 for parent in self .parents .all ():
287334 tree [parent ] = parent .ancestors_tree ()
@@ -300,9 +347,9 @@ def _roots(self, ancestors_tree):
300347
301348 def roots (self ):
302349 """
303- Returns roots nodes, if any
304- # ToDo: Modify to use CTE
350+ Returns a QuerySet of all root nodes, if any, for the current Node
305351 """
352+ # ToDo: Modify to use CTE
306353 ancestors_tree = self .ancestors_tree ()
307354 roots = set ()
308355 for ancestor in ancestors_tree :
@@ -324,9 +371,9 @@ def _leaves(self, descendants_tree):
324371
325372 def leaves (self ):
326373 """
327- Returns leaves nodes, if any
328- # ToDo: Modify to use CTE
374+ Returns a QuerySet of all leaf nodes, if any, for the current Node
329375 """
376+ # ToDo: Modify to use CTE
330377 descendants_tree = self .descendants_tree ()
331378 leaves = set ()
332379 for descendant in descendants_tree :
@@ -337,29 +384,27 @@ def leaves(self):
337384
338385 def descendants_edges (self ):
339386 """
340- Returns a queryset of descendants edges
341-
342- ToDo: Perform topological sort
387+ Returns a QuerySet of descendant Edge instances for the current Node
343388 """
389+ # ToDo: Perform topological sort
344390 return edge_model .objects .filter (
345391 parent__in = self .self_and_descendants (),
346392 child__in = self .self_and_descendants (),
347393 )
348394
349395 def ancestors_edges (self ):
350396 """
351- Returns a queryset of ancestors edges
352-
353- ToDo: Perform topological sort
397+ Returns a QuerySet of ancestor Edge instances for the current Node
354398 """
399+ # ToDo: Perform topological sort
355400 return edge_model .objects .filter (
356401 parent__in = self .self_and_ancestors (),
357402 child__in = self .self_and_ancestors (),
358403 )
359404
360405 def clan_edges (self ):
361406 """
362- Returns a queryset of all edges associated with a given node
407+ Returns a QuerySet of all Edge instances associated with a given node
363408 """
364409 return self .ancestors_edges () | self .descendants_edges ()
365410
@@ -378,44 +423,46 @@ def duplicate_edge_checker(parent, child):
378423
379424class EdgeManager (models .Manager ):
380425 def from_nodes_queryset (self , nodes_queryset ):
381- """Provided a queryset of nodes, returns all edges where a parent and child
382- node are within the queryset of nodes."""
426+ """
427+ Provided a QuerySet of nodes, returns a QuerySet of all Edge instances where a parent and child Node are within
428+ the QuerySet of nodes
429+ """
383430 return _ordered_filter (self .model .objects , ["parent" , "child" ], nodes_queryset )
384431
385432 def descendants (self , node , ** kwargs ):
386433 """
387- Returns a queryset of all edges descended from the given node
434+ Returns a QuerySet of all Edge instances descended from the given Node instance
388435 """
389436 return _ordered_filter (self .model .objects , "parent" , node .self_and_descendants (** kwargs ))
390437
391438 def ancestors (self , node , ** kwargs ):
392439 """
393- Returns a queryset of all edges which are ancestors of the given node
440+ Returns a QuerySet of all Edge instances which are ancestors of the given Node instance
394441 """
395442 return _ordered_filter (self .model .objects , "child" , node .ancestors_and_self (** kwargs ))
396443
397444 def clan (self , node , ** kwargs ):
398445 """
399- Returns a queryset of all edges for ancestors, self, and descendants
446+ Returns a QuerySet of all Edge instances for ancestors, self, and descendants
400447 """
401448 return self .from_nodes_queryset (node .clan (** kwargs ))
402449
403450 def path (self , start_node , end_node , ** kwargs ):
404451 """
405- Returns a queryset of all edges for the shortest path from start_node to end_node
452+ Returns a QuerySet of all Edge instances for the shortest path from start_node to end_node
406453 """
407454 return self .from_nodes_queryset (start_node .path (end_node , ** kwargs ))
408455
409456 def validate_route (self , edges , ** kwargs ):
410457 """
411- Given a list or set of edges , verify that they result in a contiguous route
458+ Given a list or set of Edge instances , verify that they result in a contiguous route
412459 """
413460 # ToDo: Implement
414461 pass
415462
416463 def sort (self , edges , ** kwargs ):
417464 """
418- Given a list or set of edges , sort them from root-side to leaf-side
465+ Given a list or set of Edge instances , sort them from root-side to leaf-side
419466 """
420467 # ToDo: Implement
421468 pass
0 commit comments