« | » Main « | »

Global Django requests

Saturday 28 August 2010

As my Django sites get larger and larger, there inevitably comes a point where I want access to the current request from deep inside some function that doesn't have the request object. The latest reason was that I wanted a model class helper to have access to the session so it could access some debug flags.

The first option for making this work was to pass the request object through two or three layers of code that otherwise didn't need a request. This meant changing the signature and callers of the two or three functions, which felt messy. The prospect of it made me unhappy.

The second option was to create a way for any code invoked as part of a request to get access to the request, even if it weren't passed to it explicitly. I think of this as a global request object.

Of course, there isn't really a single global request object, since there can be many threads, each of which is handling a separate request. We need a way to associate the request with a thread, and then to get the request for our thread. This middleware does the job nicely:

from django.utils.thread_support import currentThread
_requests = {}

def get_request():
    return _requests[currentThread()]

class GlobalRequestMiddleware(object):
    def process_request(self, request):
        _requests[currentThread()] = request

I didn't write this code, I got it from here and here, not sure who wrote it first. I'm also not sure why Django provides its own currentThread function when the Python standard module threading provides thread locals to acheive the same effect.

There's another way to get a global request object, but you probably won't like it:

def get_request():
    """Walk up the stack, return the nearest first argument named "request"."""
    frame = None
    try:
        for f in inspect.stack()[1:]:
            frame = f[0]
            code = frame.f_code
            if code.co_varnames and code.co_varnames[0] == "request":
                return frame.f_locals['request']
    finally:
        del frame

This function looks at the stack frames of all of its callers, looking for one with a first argument named "request". If found, it returns the value. The problem with this function is that it can be fooled, and will return the "request" it finds regardless of its type.

In its defense: GlobalRequestMiddleware requires more machinery, and adds a tiny tax to every request. If your need for the global request object is rare, the frame-based get_request() may be better for you. Also, it's a (nasty) technique that can be adapted to other situations.

Coverage.py v3.4 beta 1

Saturday 21 August 2010

Coverage.py v3.4 beta 1 is available now. 3.4 brings improved source code specification. The --omit and --include switches have changed in an incompatible way, taking file patterns rather than prefixes, and a new --source switch specifies directories or modules to measure. Details are in the new page about specifying source files in the coverage.py docs. These changes should help people focus coverage.py on the code they really want to measure.

In addition, a few bug fixes have helped in this area: Jinja templates and doctest bodies are no longer measured, since they produced errors during reporting anyway.

One other notable change: coverage.py used to report the number of statements, and the number of executed statements. Now instead of executed statements, it reports missed statements. This is a better indicator of how well your code is covered, because it's clear what the goal is: zero missed statements.

Give this version a try. There have been lots of changes. Also, I hate to admit it: but this version has broken my own coverage measurement of coverage.py itself, and I haven't figured out why yet...

Entrepreneurship

Tuesday 17 August 2010

After posting Alain De Botton's quote about meaningful work, I read his book The Joys and Sorrows of Work. It was an interesting read, but ultimately a bit empty. He described a number of unusual working environments, but managed not to get a single worker's words or thoughts onto the page. Mostly what he describes are the factual landscape, his own reactions to what he sees, and way too much about his own difficulties traveling.

But he is a thoughtful observer, and a good writer. After watching inventors pitch futilely to investors, he summed up the dynamics of venture capital thus:

Then again, there was a certain heroic beauty in the exuberant destruction of both capital and hope entailed by the entrepreneurs' activities. Money patiently accumulated through decades of unremarkable work would, in a rush of optimism inspired by a flattering business plan, be handed over to a momentarily convincing chief executive, who would hasten to set the pyre alight in a brief, brilliant and largely inconsequential blaze.

A bit cynical, but spot on.

Django superuser login trapdoor

Friday 13 August 2010

I added an admin trapdoor login to a project the other day. This is the technique where a superuser can log in to a site as any other user. My preferred way to do this is to use the standard login form in a clever way: enter the desired user's name as the username, and both your superuser name and superuser password into the password field.

But this project was modern enough that I could use a Django authentication backend to get the job done:

from django.contrib.auth import login, authenticate
from django.contrib.auth.models import User

# So I can invoked authenticate recursively below
django_authenticate = authenticate

class SuperuserLoginAuthenticationBackend(object):
    """ Let superusers login as regular users. """
    def authenticate(self, username=None, password=None):
        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            return None
        # The password should be name/password
        if "@" not in password:
            return None
        supername, superpass = password.split("@", 1)
        superuser = django_authenticate(username=supername, password=superpass)
        if superuser and superuser.is_superuser:
            return user

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

Very nice.

« | » Main « | »