Question

I will roll out in the next days an application which is using Django MPTT to manage hierarchical data. MPTT provides a function called rebuild, which rebuilds all trees available for a given model, and is called as such TreeNodes.objects.rebuild(). As you can see the command is called on the model, not on an instance of the model. This command has to be called after a node has been inserted into a tree.

For Django MPTT 0.6 (not yet officially released) a partial_rebuild command is implemented, which will only rebuild the given tree.

While testing locally with up to 10 trees it is no performance issue at all, but I'm concerned when there are 100s of trees in my db, and I'm calling the rebuild command (which will rebuild all 100s of trees), this might be a significant performance issue.

Anybody out there who has experience with using the rebuild command?

Was it helpful?

Solution

For future reference..

objects.rebuild() is only needed in some special cases. Normally mptt sets the left and right values of your nodes correctly just by setting the parent id of your node. This is also mentioned in the docs

I experienced an issue with not correctly set left, right values because I saved the new node, !before! I reset the position values of the other, already existing siblings. When inserting new nodes for a tree with the meta attribute order_insertion_by set, one has to first reset the order_insertion_by value for all siblings, and after that, save the new node. This way mptt is able to recalculate correctly the left right values.

See my (simplified) example below:

models.py

class Node(MPTTModel):
    """
    Representation of a single node
    """
    name = models.CharField(max_length=200)
    parent = TreeForeignKey('self', null=True, blank=True, related_name='%(app_label)s_%(class)s_children')
    position = models.PositiveIntegerField(max_length=10) #for nodes on the same hierarchy level we have to define the position in which they are displayed 

    class MPTTMeta:
        order_insertion_by = ['position']

views.py

new_node = Node(name="new node", parent=parent_node, position=1)
update_node_positions(new_node, 1) #routine to update the node positions
new_node.save()

update_node_positions

def update_node_positions(node, mode):
    """
    Procedure to update the node positions
    Three different modes available:
        1 = insert node
        2 = update position, parent stays the same
        3 = update position and change parent
        4 = trashed
    """

    if mode == 1 or mode==3:
        #if node has been inserted at the beginning
        if node.position == 1:
            node.get_siblings().update(position=F('position') + 1)
        #if node has been inserted not at beginning and not at the last position
        elif node.position != node.get_siblings().count() + 1:
            #update positions of siblings right of node by one
            node.get_siblings().filter(position__gte=node.position).update(position=F('position') + 1)
        if mode == 3:
            #since we removed the node from a parent, we have to decrement the positions of the former siblings right of the node by one
            if node._original_parent is not None:
                #do updates only for nodes which had a parent before. will not be executed for root nodes
                node._original_parent.get_children().filter(position__gt=node._original_position).update(position=F('position') - 1)
    if mode == 2:
        #if old position is left of new position -> decrement position by 1 for nodes which have position <= node.position AND > node.original_position
        if node.position > node._original_position:
            node.get_siblings().filter(Q(position__lte=node.position) & Q(position__gt=node._original_position)).update(position=F('position') - 1)
        #if old position is right of new position -> increment position by 1 for nodes which have position >= node.position AND < node.original_position 
        if node.position < node._original_position:
            node.get_siblings().filter(Q(position__gte=node.position) & Q(position__lt=node._original_position)).update(position=F('position') + 1)
    if mode == 4:
        #decrement position by 1 for nodes which have position > node.position
        node.get_siblings().filter(Q(position__gt=node.position)).update(position=F('position') - 1)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top