Documentation bookmarklets

Published November 7, 2007. Filed under: Django.

Django’s admin application is capable of generating and displaying a variety of useful documentation (though once newforms-admin lands, that will be broken out into a separate documentation-only application), including lists of installed models and registered template tags, and a listing of valid URL patterns and the view functions they map to, along with the docstrings of those functions. But it also contains a page of bookmarklets — bits of JavaScript to be dragged into your browser’s bookmarks bar, and then clicked from various pages on your site — which enhance this and add some other useful capabilities. Though they’re linked right off the main admin documentation page, most folks don’t ever seem to discover them or work out exactly what they do (official documentation is, admittedly, a bit sketchy), so let’s take a look at how to put them to work. Along the way we’ll see that the infrastructure they use is re-usable and offers some handy insights into the workings of your applications.

Two small prerequisites

Before you start using the bookmarklets, you should take a moment to fill in a value for the INTERNAL_IPS setting; generally, you want this to be something like the IP address(es) of your corporate firewall, since its entire purpose is to distinguish the IPs of people within your organization. This isn’t strictly necessary, but is one way of using the admin bookmarklets and also enables some other features, such as displaying debugging information in your template output. The bookmarklets will also work for any user who’s authenticated and has the is_staff flag set to True, regardless of IP address.

You’ll also want to add “django.middleware.doc.XViewMiddleware” to your MIDDLEWARE_CLASSES setting, because this sets up the automatic view-documentation bookmarklet. It’s in the default set of middleware classes, so you should have it already unless you’ve edited the setting.

What view am I looking at?

If you’ve got a large and complex URL hierarchy with lots of views, or if you’re doing some tricky routing and aren’t quite sure whether you’re getting to the right view function, it’s often incredibly helpful to have a way to find out just which view, exactly, generated the response you’re looking at. The “documentation for this page” bookmarklet does just that; click it from any URL on your site, and you’ll be taken straight to the admin documentation for the view function which responds to that URL.

This works because of the XViewMiddleware, which adds a “process_view()” handler that does the following processing:

  1. If the HTTP request method was HEAD, and
  2. Either the IP address the request came from is in INTERNAL_IPS or the user is authenticated and has the is_staff flag, then
  3. The middleware “short-circuits” processing and immediately returns an empty HttpResponse with one extra header.

The extra header it sends out is called X-View, and its value will be the dotted Python path of the view function the URL routed to; this works because process_view() middleware methods are called after URL resolution but before the view function is called; the process_view() method is passed the view function as an argument, and XViewMiddleware simply reads the __module__ and __name__ attributes of the view function to get its Python path. From there, it’s a simple matter for the JavaScript bookmarklet (which issues a HEAD to the current URL using XMLHttpRequest) to construct the admin documentation URL for the view and redirect your browser there.

Once you know about this, it’s also trivially easy to write your own code, in pretty much any language that speaks HTTP, which issues HEAD requests to URLs on your site and reads the X-View header to give you information about the view function which would have responded.

One caveat, however: views which have had decorators applied to them won’t work with this system (and don’t work well with the admin documentation in general), because a decorated view function is replaced by the new function returned from the decorator.

What object am I looking at?

It’s also handy to be able to find out what object was retrieved by the Django ORM for display on a given page, and to have shortcuts for jumping to the admin editing form for that object. The other three bookmarklets handle this:

Django’s simple and date-based object_detail generic views work with this out of the box, and you can easily add support for it to your own views as well, by using the function django.core.xheaders.populate_xheaders; here’s a simple example view which shows the necessary steps:

from django.shortcuts import get_object_or_404, render_to_response
from django.core.xheaders import populate_xheaders
from blog.models import Entry

def entry_detail(request, object_id):
    entry = get_object_or_404(Entry, pk=object_id)
    response = render_to_response('entry_detail.html',
                                  { 'entry': entry })
    populate_xheaders(request, response, Entry, entry.id)
    return response

The populate_xheaders function takes four arguments:

  1. An HttpRequest.
  2. An HttpResponse.
  3. A model class.
  4. An object id.

It then works through the same logic as XViewMiddleware, and if the request came from an IP in INTERNAL_IPS or from a logged-in staff member it populates two additional headers into the response:

  1. X-Object-Type will be a dot-separated string containing the app label and model name of the model that the object being displayed belongs to (in other words, the sort of string which conventionally gets split up into arguments to get_model(), as we saw when we looked at generic model loading).
  2. X-Object-Id will be the id of the object being displayed.

The bookmarklets, once again, are simply using XMLHttpRequest and reading headers out of the response; in this case, getting the string representing the model and the object ID is enough to display that information to the user, or to construct an admin URL for editing the object. Because Django has no way of knowing which object, precisely, is the one being “displayed”, populate_xheaders() relies on you to pass in the model class and object ID, and the request and response are required for the security check (which needs to look at the request’s IP address and user) and to populate the headers.

And, once again, this information is extremely useful outside of the admin bookmarklets; once you drop a call to populate_xheaders() into your view, there are all sorts of things you can work out from programmatically issuing a request to your application and reading back the headers to find out what object it actually retrieved out of your database.

Potential uses

Aside from their use in generating helpful documentation and jumping into the admin editing forms, these extra bits of information are invaluable for debugging and testing your applications; Django’s built-in test client is extremely useful for unit testing the output of your views, but taking advantage of XViewMiddleware and populate_xheaders() to verify your URL configuration and specific object retrievals is sometimes much simpler (and much easier to do in bulk runs) than going through the test client.

This also provides a somewhat minimal, but still useful, API for Django installations to securely (thanks to the limitations of using either INTERNAL_IPS or staff status) expose some information about themselves (and in a decently HTTP-native fashion, since it centers mostly around firing HEAD requests to particular URLs), and some pointers on how a richer application-specific API might be developed.