Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Re-ordering child nodes in django-MPTT

I'm using Ben Firshman's fork of django-MPTT (hat tip to Daniel Roseman for the recommendation).

I've got stuck trying to re-order nodes which share a common parent. I've got a list of primary keys, like this:

ids = [5, 9, 7, 3]

All of these nodes have a parent, say with primary key 1.

At present, these nodes are ordered [5, 3, 9, 7], how can I re-order them to [5, 9, 7, 3]?

I've tried something like this:

last_m = MyModel.get(pk = ids.pop(0))
last_m.move_to(last_m.parent, position='first-child')

for id in ids:
  m = MyModel.get(pk = id)
  m.move_to(last_m, position='right')

Which I'd expect to do what I want, per the docs on move_to, but it doesn't seem to change anything. Sometimes it seems to move the first item in ids to be the first child of its parent, sometimes it doesn't.

Am I right in my reading of the docs for move_to that calling move_to on a node n with position=right and a target which is a sibling of n will move n to immediately after the target?

It's possible I've screwed up my models table in trying to figure this out, so maybe the code above is actually right. It's also possible there's a much more elegant way of doing this (perhaps one that doesn't involve O(n) selects and O(n) updates).

Have I misunderstood something?

Bonus question: is there a way of forcing django-MPTT to rebuild lft and rght values for all instances of a given model?

like image 799
Dominic Rodger Avatar asked Feb 24 '10 09:02

Dominic Rodger


1 Answers

I think this is an artefact of a failure in MPTT that I've mentioned before - when you move nodes around, it correctly updates the instance of the node you're moving, but it doesn't update the instance of the target (although it does get updated in the database).

The consequence of this is that in your code, each m gets moved to the right of last_m - but the values in the last_m still reflect the position before the move, so the next move uses the original lft/right values instead of the new post-move ones.

The solution is to reload last_m each time:

for id in ids:
  last_m = MyModel.objects.get(pk=last_m.id)
  m = MyModel.get(pk = id)
  m.move_to(last_m, position='right')
like image 136
Daniel Roseman Avatar answered Sep 28 '22 06:09

Daniel Roseman