Using Django-MPTT: lessons learned…

Here we are again with Django and MPTT 0.3 (I already have other posts about it). After working with it for a bit I realized that things were breaking mysteriously, and only recently understood why that happened, so I thought I’d share this pearl of wisdom. Essentially this has to do with the way tree-elements must be created if you want the usual tree-navigation methods (e.g. get_descendants or get_ancestors) to work as expected.

Suppose your Django model looks like this:

from django.db import models

import mptt

class PossessionNew(models.Model):
    possname = models.CharField(max_length=50, unique=True)
    parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

mptt.register(PossessionNew,)

Suppose now that you want to start instantiating the PossessionNew model (sorry about the name, it’s just taken out the context of the application I was working on).

In my case I was creating the tree from other data which needed some pre-processing in order to determine the hierarchical information, so I thought I’d create first all the instances, and then ‘link’ them by setting their .parent attribute as needed. This turned out to be the wrong way of doing it.
In other words, what I did was this: first, creating the instances, and second, create the relationships. E.g.:

bash-3.2$ ./runshell.my
Python 2.5.1 (r251:54863, Sep 11 2008, 14:17:35)
[GCC 4.0.1 (Apple Computer, Inc. build 5250)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from poms.pomsapp.models import *
>>> p1 = PossessionNew(possname="test11")
>>> p2 = PossessionNew(possname="test22")
>>> p3 = PossessionNew(possname="test33")
>>> p1.save()
>>> p2.save()
>>> p3.save()
>>> p3.parent = p2
>>> p2.parent = p1
>>> p3.save()
>>> p2.save()

Everything seemed to work fine and it also looked fine on the admin interface.
However, things didn’t work when trying to use MPTT APIs. For example, after restarting the shell:

>>> p2 = PossessionNew.objects.get(possname="test22")
>>> p2.get_children()
[]  # weird!
>>> p2.get_
p2.get_ancestors               p2.get_descendants             p2.get_next_sibling            p2.get_previous_sibling
p2.get_children                p2.get_next_by_created_at      p2.get_previous_by_created_at  p2.get_root
p2.get_descendant_count        p2.get_next_by_updated_at      p2.get_previous_by_updated_at  p2.get_siblings
>>> p2.parent
<PossessionNew: test11>
>>> p2.children.all()
[<PossessionNew: test33>]
>>> p2.get_ancestors()
[<PossessionNew: test11>]
>>> p2.get_descendants()
[] # weird!

As you can see, we have some erratic behavior here, in fact the get_descendants and other similar methods don’t produce the desired output..

I soon realized that the error lies in the fact that when creating children manually – e.g. by setting the .parent attribute of an instance – the other fields needed for MPTT to manage the tree are not updated.

So, here’s the right way of doing this.
When operating with the tree you must always use the insert_at and move_to methods that come with the MPTT library. So, for example:

>>> p3 = PossessionNew(possname = "test333")
>>> p1 = PossessionNew(possname = "test111")
>>> p2 = PossessionNew(possname = "test222")
>>> p1.save()
>>> p2.save()
>>> p3.save()
>>> p2.move_to(p1)
>>> p3.move_to(p2)

[Update 17/11/10:] almost by chance, I finally understood what the problem was here.. mptt has a problem with updating instances already stored in memory. What follows is still valid, but there are other ways around the problem too eg. check this link too.

Now let’s check again whether the model inheritance works all right (I usually have to restart the shell in order to check this, otherwise the modifications are not loaded properly. I haven’t figured out yet why this happens…):

>>> p2 = PossessionNew.objects.get(possname = "test222")
>>> p2.get_descendants()
[<PossessionNew: test333>]
>>> p2.get_ancestors()
[<PossessionNew: test111>]

Now it all makes more sense, doesn’t it?
Notice that in this case we used the move_to method. We could also have used insert_at (check the docs). Remember also that we’ve been using these methods with instances up to now (=instance methods). If necessary, you could also achieve the same results by means of the TreeManager custom manager (=custom manager methods).
So, for example:

>>> p1 = PossessionNew(possname = "test11")
>>> PossessionNew.tree.insert_node(p1, None, commit=True)
<PossessionNew: test11>
>>> p2 = PossessionNew(possname = "test22")
>>> PossessionNew.tree.insert_node(p2, p1, commit=True)
<PossessionNew: test22>
>>> p3 = PossessionNew(possname = "test33")
>>> PossessionNew.tree.insert_node(p3, p2, commit=True)
<PossessionNew: test33>

That’s it. Follows a list of the model instance methods MPTT makes available:

get_ancestors(ascending=False) — creates a QuerySet containing the ancestors of the model instance. These default to being in descending order (root ancestor first, immediate parent last); passing True for the ascending argument will reverse the ordering (immediate parent first, root ancestor last).

get_children() — creates a QuerySet containing the immediate children of the model instance, in tree order. The benefit of using this method over the reverse relation provided by the ORM to the instance’s children is that a database query can be avoided in the case where the instance is a leaf node (it has no children).

get_descendants(include_self=False) — creates a QuerySet containing descendants of the model instance, in tree order.
If include_self is True, the QuerySet will also include the model instance itself.

get_descendant_count() — returns the number of descendants the model instance has, based on its left and right tree node edge indicators. As such, this does not incur any database access.

get_next_sibling() — returns the model instance’s next sibling in the tree, or None if it doesn’t have a next sibling.

get_previous_sibling() — returns the model instance’s previous sibling in the tree, or None if it doesn’t have a previous sibling.

get_root() — returns the root node of the model instance’s tree.

get_siblings(include_self=False) — creates a QuerySet containing siblings of the model instance. Root nodes are considered to be siblings of other root nodes. If include_self is True, the QuerySet will also include the model instance itself.

insert_at(target, position=’first-child’, commit=False) — positions the model instance (which must not yet have been inserted into the database) in the tree based on target and position (when appropriate). If commit is True, the model instance’s save() method will be called before the instance is returned.

is_child_node() — returns True if the model instance is a child node, False otherwise.

is_leaf_node() — returns True if the model instance is a leaf node (it has no children), False otherwise.

is_root_node() — returns True if the model instance is a root node, False otherwise.

move_to(target, position=’first-child’) — moves the model instance elsewhere in the tree based on target and position (when appropriate).

 

Share/Bookmark






10 Responses to “Using Django-MPTT: lessons learned…”

Hey there,
Great site, I just found it and I am already a fan.

Computers & Tech added these pithy words on Sep 19 09 at 7:10 am

cool!

magicrebirth added these pithy words on Sep 28 09 at 11:59 am

Thanks for that. Just starting with mptt and I think you saved me from a lot of frustration!

Phoebe Bright added these pithy words on Sep 29 09 at 8:59 pm

Just wondered if you had experimented with mptt and inherited models. Havn’t been able to get it working myself and looking around to see if anyone else has.
Abstract inheritance will work only if you want the child classes to only have parents in the same child class. Non-abstract inheritance doesn’t work at all.

This is where I got to:
http://groups.google.com/group/django-users/browse_thread/thread/777c903177d09970#

Phoebe Bright added these pithy words on Oct 05 09 at 10:20 am

hi phoebe – no I didn’t do much experimentation with that, although I noticed that normal inheritance didn’t work. I suppose that’s because the ‘lft’, ‘rgt’ fields (and the other ones MPTT creates by default for managing the hierarchy) are being created only in the superclasses… maybe the other django-tree framework does better in this respect [http://code.google.com/p/django-treebeard/] ?
Let me know how it goes!

magicrebirth added these pithy words on Oct 05 09 at 11:28 am

Great info here on mptt! This is the only example I can recall seeing where someone showed an actual real world model using mptt. In your example, would not one also need to update the parent attribute as well after using move_to so that both methods would work afterward? Maybe something could be added to the model to automatically do move_to if the parent object changes…

mike added these pithy words on Oct 08 09 at 2:58 pm

thank you! i made this exact same mistake and it bit me in the ass. Now i know. The docs aren’t very clear on this part:

always use insert_at or move_to!

brett added these pithy words on Oct 21 09 at 9:00 pm

edit !
p1 = PossessionNew(possname=”test11″)
->
p1 = PossessionNew(name=”test11″)

ganbolor added these pithy words on Jan 09 10 at 10:44 am

thanks!

mike added these pithy words on Nov 04 10 at 5:16 pm

Thanks a ton !

Kiran Ruth R added these pithy words on Aug 27 12 at 11:20 am