Quickstart Example ================== models.py ^^^^^^^^^ :: from django.db import models from django_postgresql_dag.models import node_factory, edge_factory class EdgeSet(models.Model): # Not required, but provides a convenient way of grouping Edges name = models.CharField(max_length=100, unique=True) def __str__(self): return self.name class NodeSet(models.Model): # Not required, but provides a convenient way of grouping Nodes name = models.CharField(max_length=100, unique=True) def __str__(self): return self.name class NetworkEdge(edge_factory("NetworkNode", concrete=False)): name = models.CharField(max_length=100, unique=True) edge_set = models.ForeignKey( EdgeSet, on_delete=models.CASCADE, null=True, blank=True, related_name="edge_set_edges", ) def __str__(self): return self.name def save(self, *args, **kwargs): self.name = f"{self.parent.name} {self.child.name}" super().save(*args, **kwargs) class NetworkNode(node_factory(NetworkEdge)): name = models.CharField(max_length=100) node_set = models.ForeignKey( NodeSet, on_delete=models.CASCADE, null=True, blank=True, related_name="node_set_nodes", ) def __str__(self): return self.name Optional arguments on the Edge model ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ```disable_circular_check```: Defaults to False. If set to True, django-postgresql-dag will not check for circular paths. Essentially, the resulting graph may no longer be a DAG. ```allow_duplicate_edges```: Defaults to True. Determines whether two nodes are allowed to have more than one Edge directly connecting them. Add some Instances via the Shell (or in views, etc) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: >>> from myapp.models import NetworkNode, NetworkEdge >>> root = NetworkNode.objects.create(name="root") >>> a1 = NetworkNode.objects.create(name="a1") >>> a2 = NetworkNode.objects.create(name="a2") >>> a3 = NetworkNode.objects.create(name="a3") >>> b1 = NetworkNode.objects.create(name="b1") >>> b2 = NetworkNode.objects.create(name="b2") >>> b3 = NetworkNode.objects.create(name="b3") >>> b4 = NetworkNode.objects.create(name="b4") >>> c1 = NetworkNode.objects.create(name="c1") >>> c2 = NetworkNode.objects.create(name="c2") >>> root.add_child(a1) >>> root.add_child(a2) >>> a3.add_parent(root) # You can add from either side of the relationship >>> b1.add_parent(a1) >>> a1.add_child(b2) >>> a2.add_child(b2) >>> a3.add_child(b3) >>> a3.add_child(b4) >>> b3.add_child(c2) >>> b3.add_child(c1) >>> b4.add_child(c1) Add Edges and Nodes to EdgeSet and NodeSet models (FK) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: >>> y = EdgeSet.objects.create() >>> y.save() >>> c1_ancestors = c1.ancestors_edges() >>> for ancestor in c1_ancestors: >>> ancestor.edge_set = y >>> ancestor.save() >>> x = NodeSet.objects.create() >>> x.save() >>> root.node_set = x >>> root.save() >>> a1.node_set = x >>> a1.save() >>> b1.node_set = x >>> b1.save() >>> b2.node_set = x >>> b2.save() Resulting Database Tables ^^^^^^^^^^^^^^^^^^^^^^^^^ myapp_networknode """"""""""""""""" :: id | name ----+------ 1 | root 2 | a1 3 | a2 4 | a3 5 | b1 6 | b2 7 | b3 8 | b4 9 | c1 10 | c2 myapp_networkedge """"""""""""""""" :: id | child_id | parent_id | name ----+----------+-----------+--------- 1 | 2 | 1 | root a1 2 | 3 | 1 | root a2 3 | 4 | 1 | root a3 4 | 5 | 2 | a1 b1 5 | 6 | 2 | a1 b2 6 | 6 | 3 | a2 b2 7 | 7 | 4 | a3 b3 8 | 8 | 4 | a3 b4 9 | 10 | 7 | b3 c2 10 | 9 | 7 | b3 c1 11 | 9 | 8 | b4 c1 Diagramatic View ^^^^^^^^^^^^^^^^ .. image:: https://raw.githubusercontent.com/OmenApps/django-postgresql-dag/master/docs/images/graph.png :align: center :alt: Diagram of Resulting Graph Work with the Graph in the Shell (or in views, etc) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: >>> from myapp.models import NetworkNode, NetworkEdge # Descendant methods which return a queryset >>> root.descendants() , , , , , , , , ]> >>> root.descendants(max_depth=1) , , ]> >>> root.self_and_descendants() , , , , , , , , , ]> >>> root.descendants_and_self() [, , , , , , , , , ] # Ancestor methods which return a queryset >>> c1.ancestors() , , , ]> >>> c1.ancestors(max_depth=2) , , ]> >>> c1.ancestors_and_self() , , , , ]> >>> c1.self_and_ancestors() [, , , , ] # Get the node's clan (all ancestors, self, and all descendants) >>> b3.clan() , , , , ]> # Get all roots or leaves associated with the node >>> b3.roots() {} >>> b3.leaves() {, } # Perform path search >>> root.path(c1) , , , ]> >>> root.path(c1, max_depth=2) # c1 is 3 levels deep from root Traceback (most recent call last): File "", line 1, in root.path(c1, max_depth=2) File "/home/runner/pgdagtest/pg/models.py", line 550, in path ids = [item.id for item in self.path_raw(target_node, **kwargs)] File "/home/runner/pgdagtest/pg/models.py", line 546, in path_raw raise NodeNotReachableException pg.models.NodeNotReachableException >>> root.path(c1, max_depth=3) , , , ]> # Reverse (upward) path search >>> c1.path(root) # Path defaults to top-down search, unless `directional` is set to False Traceback (most recent call last): File "", line 1, in c1.path(root) File "/home/runner/pgdagtest/pg/models.py", line 548, in path ids = [item.id for item in self.path_raw(target_node, **kwargs)] File "/home/runner/pgdagtest/pg/models.py", line 544, in path_raw raise NodeNotReachableException pg.models.NodeNotReachableException >>> c1.path(root, directional=False) , , , ]> >>> root.distance(c1) 3 # Check node properties >>> root.is_root() True >>> root.is_leaf() False >>> root.is_island() False >>> c1.is_root() False >>> c1.is_leaf() True >>> c1.is_island() False # Get ancestors/descendants tree output >>> a2.descendants_tree() {: {}} >>> root.descendants_tree() {: {: {}, : {}}, : {: {}}, : {: {: {}, : {}}, : : {}}}} >>> root.ancestors_tree() {} >>> c1.ancestors_tree() {: {: {: {}}}, : {: {: {}}}} >>> c2.ancestors_tree() {: {: {: {}}}} # Get a queryset of edges relatd to a particular node >>> a1.ancestors_edges() ]> >>> b4.descendants_edges() ]> >>> b4.clan_edges() , , ]> # Get the nodes at the start or end of an edge >>> e1.parent >>> e1.child >>> e2.parent >>> e2.child # Edge-specific Manager methods >>> NetworkEdge.objects.descendants(b3) , ]> >>> NetworkEdge.objects.ancestors(b3) , ]> >>> NetworkEdge.objects.clan(b3) , , , ]> >>> NetworkEdge.objects.path(root, c1) , , ]> >>> NetworkEdge.objects.path(c1, root) # Path defaults to top-down search, unless `directional` is set to False Traceback (most recent call last): File "", line 1, in NetworkEdge.objects.path(c1, root) File "/home/runner/pgdagtest/pg/models.py", line 677, in path start_node.path(end_node), File "/home/runner/pgdagtest/pg/models.py", line 548, in path ids = [item.id for item in self.path_raw(target_node, **kwargs)] File "/home/runner/pgdagtest/pg/models.py", line 544, in path_raw raise NodeNotReachableException pg.models.NodeNotReachableException >>> NetworkEdge.objects.path(c1, root, directional=False) , , ]>