Methods on Node and Edge

Listed below are the methods that are useful for building/manipulating/querying the graph. We ignore here the methods used only for internal functionality.

Node

Manager Methods

roots(self, node=None)

Returns a Queryset of all root nodes (nodes with no parents) in the Node model. If a node instance is specified, returns only the roots for that node.

leaves(self, node=None)

Returns a Queryset of all leaf nodes (nodes with no children) in the Node model. If a node instance is specified, returns only the leaves for that node.

Model Methods

Methods used for building/manipulating

add_child(self, child, **kwargs)

Provided with a Node instance, attaches that instance as a child to the current Node instance

remove_child(self, child, delete_node=False)

Removes the edge connecting this node to child if a child Node instance is provided, otherwise removes the edges connecting to all children. Optionally deletes the child(ren) node(s) as well.

add_parent(self, parent, \*args, **kwargs)

Provided with a Node instance, attaches the current instance as a child to the provided Node instance

remove_parent(self, parent, delete_node=False)

Removes the edge connecting this node to parent if a parent Node instance is provided, otherwise removes the edges connecting to all parents. Optionally deletes the parent node(s) as well.

Methods used for querying

ancestors(self, **kwargs)

Returns a QuerySet of all nodes in connected paths in a rootward direction

ancestors_count(self)

Returns an integer number representing the total number of ancestor nodes

self_and_ancestors(self, **kwargs)

Returns a QuerySet of all nodes in connected paths in a rootward direction, prepending with self

ancestors_and_self(self, **kwargs)

Returns a QuerySet of all nodes in connected paths in a rootward direction, appending with self

descendants(self, **kwargs)

Returns a QuerySet of all nodes in connected paths in a leafward direction

descendants_count(self)

Returns an integer number representing the total number of descendant nodes

self_and_descendants(self, **kwargs)

Returns a QuerySet of all nodes in connected paths in a leafward direction, prepending with self

descendants_and_self(self, **kwargs)

Returns a QuerySet of all nodes in connected paths in a leafward direction, appending with self

clan(self, **kwargs)

Returns a QuerySet with all ancestors nodes, self, and all descendant nodes

clan_count(self)

Returns an integer number representing the total number of clan nodes

siblings(self)

Returns a QuerySet of all nodes that share a parent with this node, excluding self

siblings_count(self)

Returns count of all nodes that share a parent with this node

siblings_with_self(self)

Returns a QuerySet of all nodes that share a parent with this node and self

partners(self)

Returns a QuerySet of all nodes that share a child with this node, excluding self

partners_count(self)

Returns count of all nodes that share a child with this node

partners_with_self(self)

Returns a QuerySet of all nodes that share a child with this node and self

path_exists(self, ending_node, **kwargs)

Given an ending Node instance, returns a boolean value determining whether there is a path from the current Node instance to the ending Node instance

Optional keyword argument: directional (boolean: if True, path searching operates normally, in a leafward only direction. If False, search operates in both directions)

path(self, ending_node, **kwargs)

Returns a QuerySet of the shortest path from self to ending node, optionally in either direction. The resulting Queryset is sorted from root-side, toward leaf-side, regardless of the relative position of starting and ending nodes.

Optional keyword argument: directional (boolean: if True, path searching operates normally, in a leafward only direction. If False, search operates in both directions)

distance(self, ending_node, **kwargs)

Returns the shortest hops count to the target node

is_root(self)

Returns True if the current Node instance has children, but no parents

is_leaf(self)

Returns True if the current Node instance has parents, but no children

is_island(self)

Returns True if the current Node instance has no parents nor children

is_ancestor_of(self, ending_node, **kwargs)

Provided an ending_node Node instance, returns True if the current Node instance and is an ancestor of the provided Node instance

Optional keyword argument: directional (boolean: if True, path searching operates normally, in a leafward only direction. If False, search operates in both directions)

is_descendant_of(self, ending_node, **kwargs)

Provided an ending_node Node instance, returns True if the current Node instance and is a descendant of the provided Node instance

Optional keyword argument: directional (boolean: if True, path searching operates normally, in a leafward only direction. If False, search operates in both directions)

is_sibling_of(self, ending_node)

Provided an ending_node Node instance, returns True if the provided Node instance and the current Node instance share a parent Node

is_partner_of(self, ending_node)

Provided an ending_node Node instance, returns True if the provided Node instance and the current Node instance share a child Node

node_depth(self)

Returns an integer representing the depth of this Node instance from furthest root

Not yet implemented

connected_graph(self, **kwargs)

Returns a QuerySet of all nodes connected in any way to the current Node instance

connected_graph_node_count(self, **kwargs)

Returns the number of nodes in the graph connected in any way to the current Node instance

descendants_tree(self)

Returns a tree-like structure with descendants for the current Node

ancestors_tree(self)

Returns a tree-like structure with ancestors for the current Node

roots(self)

Returns a QuerySet of all root nodes, if any, for the current Node

leaves(self)

Returns a QuerySet of all leaf nodes, if any, for the current Node

descendants_edges(self)

Returns a QuerySet of descendant Edge instances for the current Node

ancestors_edges(self)

Returns a QuerySet of ancestor Edge instances for the current Node

clan_edges(self)

Returns a QuerySet of all Edge instances associated with a given node

Edge

Manager Methods

from_nodes_queryset(self, nodes_queryset)

Provided a QuerySet of nodes, returns a QuerySet of all Edge instances where a parent and child Node are within the QuerySet of nodes

descendants(self, node, **kwargs)

Returns a QuerySet of all Edge instances descended from the given Node instance

ancestors(self, node, **kwargs)

Returns a QuerySet of all Edge instances which are ancestors of the given Node instance

clan(self, node, **kwargs)

Returns a QuerySet of all Edge instances for ancestors, self, and descendants

path(self, start_node, end_node, **kwargs)

Returns a QuerySet of all Edge instances for the shortest path from start_node to end_node

validate_route(self, edges, **kwargs)

Given a list or set of Edge instances, verify that they result in a contiguous route

Not yet implemented.

sort(self, edges, **kwargs)

Given a list or set of Edge instances, sort them from root-side to leaf-side

Not yet implemented.

insert_node(self, edge, node, clone_to_rootside=False, clone_to_leafside=False, pre_save=None, post_save=None)

Inserts a node into an existing Edge instance. Returns a tuple of the newly created rootside_edge (parent to the inserted node) and leafside_edge (child to the inserted node).

Process:

  1. Add a new Edge from the parent Node of the current Edge instance to the provided Node instance, optionally cloning properties of the existing Edge.

  2. Add a new Edge from the provided Node instance to the child Node of the current Edge instance, optionally cloning properties of the existing Edge.

  3. Remove the original Edge instance.

The instance will still exist in memory, though not in database (https://docs.djangoproject.com/en/3.1/ref/models/instances/#refreshing-objects-from-database). Recommend running the following after conducting the deletion:

del instancename

Cloning will fail if a field has unique=True, so a pre_save function can be passed into this method. Likewise, a post_save function can be passed in to rebuild relationships. For instance, if you have a name field that is unique and generated automatically in the model’s save() method, you could pass in a the following pre_save function to clear the name prior to saving the new Edge instance(s):

def pre_save(new_edge):
    new_edge.name = ""
    return new_edge

A more complete example, where we have models named NetworkEdge & NetworkNode, and we want to insert a new Node (n2) into Edge e1, while copying e1’s field properties (except name) to the newly created rootside Edge instance (n1 to n2) is shown below.

Original        Final

n1  o           n1  o
    |                 \
    |                  o n2
    |                 /
n3  o           n3  o
from myapp.models import NetworkEdge, NetworkNode

n1 = NetworkNode.objects.create(name="n1")
n2 = NetworkNode.objects.create(name="n2")
n3 = NetworkNode.objects.create(name="n3")

# Connect n3 to n1
n1.add_child(n3)

e1 = NetworkEdge.objects.last()

# function to clear the `name` field, which is autogenerated and must be unique
def pre_save(new_edge):
    new_edge.name = ""
    return new_edge

NetworkEdge.objects.insert_node(e1, n2, clone_to_rootside=True, pre_save=pre_save)