Skip to content

Commit 91345df

Browse files
committed
Added an alternative rebuild_mptt_direct command
This version from the django-mptt issue tracker is much faster and doesn't crash with unhandled exceptions but it directly accesses the database
1 parent b7c6efe commit 91345df

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# encoding: utf-8
2+
3+
import logging
4+
5+
from django.core.management.base import NoArgsCommand
6+
from django.db import transaction, connection, backend
7+
8+
from feincms.module.page.models import Page
9+
10+
class Command(NoArgsCommand):
11+
help = "Manually rebuild MPTT hierarchy - should only be used to repair damaged databases"
12+
13+
@transaction.commit_manually
14+
def handle_noargs(self, **options):
15+
logging.info("Rebuilding all MPTT trees")
16+
try:
17+
rebuild()
18+
transaction.commit()
19+
except backend.DatabaseError:
20+
logging.exception("Unable to rebuild MPTT tree due to exception: rolling back all changes")
21+
transaction.rollback()
22+
23+
# TODO: Move this into utils and add a post-migrate/syncdb signal handler
24+
# which can automatically rebuild after a fixture load?
25+
26+
# Based heavily on the code from http://code.google.com/p/django-mptt/issues/detail?id=13
27+
qn = connection.ops.quote_name
28+
29+
def rebuild():
30+
"""
31+
Rebuilds whole tree in database using `parent` link.
32+
"""
33+
opts = Page._meta
34+
tree = Page.tree
35+
36+
cursor = connection.cursor()
37+
cursor.execute('UPDATE %(table)s SET %(left)s = 0, %(right)s = 0, %(level)s = 0, %(tree_id)s = 0' % {
38+
'table': qn(opts.db_table),
39+
'left': qn(opts.get_field(tree.left_attr).column),
40+
'right': qn(opts.get_field(tree.right_attr).column),
41+
'level': qn(opts.get_field(tree.level_attr).column),
42+
'tree_id': qn(opts.get_field(tree.tree_id_attr).column)
43+
})
44+
45+
cursor.execute('SELECT %(id_col)s FROM %(table)s WHERE %(parent_col)s is NULL %(orderby)s' % {
46+
'id_col': qn(opts.pk.column),
47+
'table': qn(opts.db_table),
48+
'parent_col': qn(opts.get_field(tree.parent_attr).column),
49+
'orderby': 'ORDER BY ' + ', '.join([qn(field) for field in opts.order_insertion_by]) if opts.order_insertion_by else ''
50+
})
51+
52+
idx = 0
53+
for (pk, ) in cursor.fetchall():
54+
idx += 1
55+
_rebuild_helper(pk, 1, idx)
56+
transaction.commit_unless_managed()
57+
58+
def _rebuild_helper(pk, left, tree_id, level=0):
59+
opts = Page._meta
60+
tree = Page.tree
61+
right = left + 1
62+
63+
cursor = connection.cursor()
64+
cursor.execute('SELECT %(id_col)s FROM %(table)s WHERE %(parent_col)s = %(parent)d %(orderby)s' % {
65+
'id_col': qn(opts.pk.column),
66+
'table': qn(opts.db_table),
67+
'parent_col': qn(opts.get_field(tree.parent_attr).column),
68+
'parent': pk,
69+
'orderby': 'ORDER BY ' + ', '.join([qn(field) for field in opts.order_insertion_by]) if opts.order_insertion_by else ''
70+
})
71+
72+
for (child_id, ) in cursor.fetchall():
73+
right = _rebuild_helper(child_id, right, tree_id, level+1)
74+
75+
cursor.execute("""
76+
UPDATE %(table)s
77+
SET
78+
%(left_col)s = %(left)d,
79+
%(right_col)s = %(right)d,
80+
%(level_col)s = %(level)d,
81+
%(tree_id_col)s = %(tree_id)d
82+
WHERE
83+
%(pk_col)s = %(pk)s
84+
""" % {
85+
'table': qn(opts.db_table),
86+
'pk_col': qn(opts.pk.column),
87+
'left_col': qn(opts.get_field(tree.left_attr).column),
88+
'right_col': qn(opts.get_field(tree.right_attr).column),
89+
'level_col': qn(opts.get_field(tree.level_attr).column),
90+
'tree_id_col': qn(opts.get_field(tree.tree_id_attr).column),
91+
'pk': pk,
92+
'left': left,
93+
'right': right,
94+
'level': level,
95+
'tree_id': tree_id
96+
})
97+
98+
return right + 1

0 commit comments

Comments
 (0)