Making admin bulk delete action work with django-mptt

For the past month and a half I have been learning Django (and therefore Python…) at my new job.

I’m very impressed how quick it has been to learn, and particularly with how complete and well thought-through the Django framework is. Python is nice too, I’m not missing all the $ signs, semi-colons and curly braces at all :)

For the project I’ve been working on we used django-mptt to manage a ‘Modified Pre-Order Tree Traversal‘ scheme for a heirarchical Category model.

django-mptt works great but to properly manage an mptt model in the django admin you have to do a bit of hacking:

  1. Following the instructions here, we borrow some code from the FeinCMS app to add tree control to the admin. See update at end of article for an extra hack you may need.
  2. At the bottom of that article they list some extra tweaks to put ‘add child’ and ‘preview’ icons in the actions column, which are worth doing.
  3. At this point your mptt admin is working pretty well. One problem you find is if you use the actions drop-down menu to ‘delete selected’ items from your mptt model… the bulk actions bypass the model’s delete method so your left/right values for the tree aren’t updated.
  4. What you need is a way to rebuild the tree after a bulk action… looking around the django-mptt site you can find a patch in this thread which adds a ‘rebuild’ method to the tree manager. Go ahead and patch!
  5. Finally we want to hack the Django admin’s default ‘delete_selected’ action so it calls the rebuild method. Once you’ve got the FeinCMS and patched django-mptt the code to pull it all together is pretty simple: http://www.djangosnippets.org/snippets/1775/

That’s it… your actual model admin class can then inherit off this one.

I’ve been loving the multiple inheritance in Python actually. I’ve found it particularly useful with the Django models and model admin classes. For example a model may have mptt tree functionality, it may also use django-tagging (another very handy app!) and the django contrib.comments app.

Each of these requires a bit of specialised admin functionality and if you have several models which use those apps, in different combinations, then you want to be able to re-use that code in a granular way. With multiple inheritance I can just say:

CategoryAdmin(MPTTModelAdmin, TaggableModelAdmin):
    ...

BlogPostAdmin(TaggableModelAdmin, CommentableModelAdmin):
    ...

I like this a lot. Incidentally, if you’re using django-tagging you want to have a look at this auto-complete widget – I think it’s a good idea to use something like that to help prevent duplicate tags with slightly different spelling etc.

Update (30 Oct 09):

Something I forget, which you may need to do. After step (1) you may need to do a hack to the feincms/admin/tree_editor.py … Fein CMS assumes you use the default ‘parent’ attribute name in your mptt tree model, which works for them, but mptt gives you the possibility to change this in the model’s Meta class so we need to make the tree_editor code more generic.

There’s a few edits on lines approx 50-60 in tree_editor.py:

def _build_tree_structure(cls):
    """
    Build an in-memory representation of the item tree, trying to keep
    database accesses down to a minimum. The returned dictionary looks like
    this (as json dump):

        {"6": {"id": 6, "children": [7, 8, 10], "parent": null, "descendants": [7, 12, 13, 8, 10]},
         "7": {"id": 7, "children": [12], "parent": 6, "descendants": [12, 13]},
         "8": {"id": 8, "children": [], "parent": 6, "descendants": []},
         ...

    """
    all_nodes = { }
    def add_as_descendant(n, p):
        if not n: return
        all_nodes[n.id]['descendants'].append(p.id)
        add_as_descendant(getattr(n, cls._meta.parent_attr), p)

    for p in cls.objects.order_by('tree_id', 'lft'):
        parent = getattr(p, cls._meta.parent_attr)
        if parent:
            all_nodes[p.id] = { 'id': p.id, 'children' : [ ], 'descendants' : [ ], 'parent' : parent.id }
            if parent.id:
                all_nodes[parent.id]['children'].append(p.id)
                add_as_descendant(parent, p)
        else:
            all_nodes[p.id] = { 'id': p.id, 'children' : [ ], 'descendants' : [ ], 'parent' : None }

    return all_nodes

I’d really like to be able to apply that change without modifying the original Fein CMS files. If anyone knows an elegant way to do so please let me know.

As a Python noob the best way I could come up with is to make my own TreeEditor class that inherits from the Fein CMS one and then override the ‘changelist_view’ method (ie copy and paste it but point it to my version of _build_tree_structure).

Update (2 Nov 09):

I sent the neccessary code change as a patch to Matthias of FeinCMS and it’s now been applied, so if you’ve got a fresh copy of the source there’s no more need to hack the tree_editor.py!

About these ads

About anentropic
songwriter, musician, and er web programmer...

5 Responses to Making admin bulk delete action work with django-mptt

  1. Pingback: More django-mptt goodness: FilteredSelectMultiple m2m widget « Anentropic Blog

  2. You could inherit TreeEditor class from tree_editor.py and override changelist_view method instead of modifying original file.
    Did you purpose this fix to FeinCMS project?

    Thanks for the post! It was very helpful.

    • anentropic says:

      I was trying to avoid doing that because you ending up copy and pasting nearly the whole method just to change a couple of lines… doesn’t feel very elegant.

      The good news is I submitted the hack I needed (which helps make the FeinCMS code more generalised) to FeinCMS and they rolled it into their source.

      AFAIK if you got your FeinCMS code since 2 Nov 09 you no longer need to patch their TreeEditor to work with non-standard mptt field names.

  3. Angela Jones says:

    Thanks for this post. I am new at django and this will be a big help.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: