Proxying Django's admin views

08 August 2008

admin, django, django-treemenus


In this post I share some thoughts on one way to customise the Django's admin interface beyond what, I believe, it was originally designed for. Well, at least it's an approach that I used to bring django-treemenus' codebase up to the NewForms-Admin's API, while preserving the app's original behaviour.

First, you may want to check the latest release of django-treemenus (0.6). In that release I've completely refactored the code to use all the goodness of NFA. Backward incompatible changes are minimal if you weren't using the extension system, and from the user's point of view everything is pretty much the same as before. The result is quite satisfactory: the amount of code was reduced by more than half, every known issue was fixed, and it is now much easier to extend/hack this app for those who are interested.

Doing that refactoring made me realise even more how great NFA is. Still, I did not quite want to use it the "standard" way. Basically, I wanted to keep the URL scheme that was used in previous versions of treemenus. For example:

/admin/treemenus/menu/1/            -> The menu #1 edit page.
/admin/treemenus/menu/1/items/add/  -> Add an item to menu #1.
/admin/treemenus/menu/1/items/9/    -> The item #9 edit page, within menu #1.

Also, I did not want to allow the items to be edited directly without the context of the menu they belong to. Therefore, I wanted to both avoid having a MenuItemAdmin class freely accessible from the admin's index page, and avoid enabling the following URLs:

/admin/treemenus/menuitem/
/admin/treemenus/menuitem/9/

To achieve that, I have first overriden the call method in the customised MenuAdmin class. I wish this could be done a bit more cleanly, so I'll probably open a ticket one day, proposing to add a simple extra hook which would greatly simplify the customisation of URL routing in the admin.

Then, because every single request would systematically be routed to the MenuAdmin class, I've used a private instance -- that is, not "officially" registered -- of MenuItemAdmin as a proxy to manipulate the menu items. For, example, here's how the MenuItemAdmin's add_view is proxied:

def add_menu_item(self, request, menu_pk):
    ...
    menuitem_admin = MenuItemAdmin(MenuItem, self.admin_site, menu)
    return menuitem_admin.add_view(request, extra_context={ 'menu': menu })

To understand how it works, let's follow the route that is taken when an item is added to a given menu. First, the URL to visit is /admin/treemenus/menu/1/items/add/. This will be routed to the MenuAdmin's __call__ method, which in turn will pass on the request to the above-mentioned add_menu_item method. There, a private instance of MenuItemAdmin is created and the request is passed on to its own add_view method. After that, NFA takes over and does its wonders to process the form and create the new item in database. The same approach is applied for all the other views: change, delete and the custom move up/down.

All this may sound complicated, but it is in fact pretty simple. If you're interested, it's probably best to check out the source code as it should speak for itself. At least, it will probably speak better than I've tried to in this post :)

NFA is a fantastic improvement to the Django's admin system, and browsing into its depths taught me some good lessons and good practices in Python and Django programming. Now, I also believe that there is still some room for a few simple backward compatible changes that would greatly improve its customisability. All the "hacks" I've done here would then become trivial, and that would open many opportunities for customising admin apps. Anyway, I'll probably post more about that in a few weeks, when things "settle down" a bit after the awesome and most anticipated Django 1.0 is released.