Monday, July 11, 2011

FeinCMS and Fixtures: Check your trees

A short while ago, I finished my first FeinCMS website. While building the website, I was surprised by the way the tree of pages, subpages, subsubpages and so on, is being saved into the database. FeinCMS turned out to be using something called 'Modified Pre-order Tree Traversal'. Google or Bing or whatever for 'MPTT' and you will find out how it works, if you don't know it already. It seems I wasn't paying a lot of attention in mathematics classes, because all my colleagues seemed to know about its existence.

Anyway, a FeinCMS page's location in a tree will be stored in the database, using a tree_id, level (0 being the top level, 1 the row beneath and so on), parent_id and lft and rght. Lft and rght are obviously the numbers left and right of the tree entry, following the MPTT principle. If you somehow want to upset the tree and FeinCMS with it, all you have to do is change one of the lft or rght numbers in the database.

This totally failed to bubble into my mind when I heard about a FeinCMS page that was not displaying. Confused, I tried to reproduce the bug, without result. After a while trying and fiddling, a colleague pointed to the MPTT principle and that FeinCMS uses it. After a little more fiddling, we found out that the tree was indeed a little 'borked', confusing FeinCMS so much that it eventually rendered a 404-page. Lucky for us, FeinCMS plugs two management commands into Django, to fix problems like this: rebuild_mptt ('Only use in emergencies') and rebuild_mptt_direct ('should only be used to repair damaged databases').

Now that the problem was fixed, all we needed to find was why the tree fell apart in the first place. The smart colleague I mentioned before, pointed at fixtures. I made an initial_data fixture to make sure some of the pages were already there for the customer. In the fixture, I tried to kind of build the tree (using tree_id, parent_id, lft, rght and level), to make sure the pages were ordered as I wanted them to be. However, I forgot that initial_data fixtures will be executed every time you run migrations. It turned out the customer had added a new page to the tree before we ran a migration for an update of the site, replacing the new rght and lft numbers with the ones from the fixture. Renaming the initial_data fixture to a more convenient name took care of this risk.

A lot was learned today.