One of my Django projects contains a function that looks like this:
1 2 3 4
def get_default_language_code(): default_lang = Language.objects.filter(default=True).first() if default_lang is not None: return default_lang.code
This function gets called multiple times, and it will hit the database on each call. This is obviously inefficient. Also, if the values in the database change while a request is being processed, the function could return different values within one request. In this example, that is unlikely to be a problem, but in other scenarios, this could lead to bugs that are hard to track down.
So I wanted to cache the return value of this function for the lifetime of the current request, but not longer. One idea was to cache it for a really short amount of time and just assume that this would mean "the current request". But that seemed unelegant and fragile. So I googled for possibilites to do per-request caching in Django and found a number of possible solutions, for example on Stackoverflow. I did not like any of them. They either seemed hackish and fragile, or unnecessarily complex just to cache a single value.
After a short discussion on the #django IRC channel about the details of django.utils.functional, Florian Apolloner suggested the somewhat obvious solution that I had failed to see so far: just cache the value as an attribute on the request. This is also what Django itself does with request.user. So I ended up doing this:
1 2 3 4
class DefaultLanguageMiddleware(object): def process_request(self, request): request.default_language_code = get_default_language_code()
And that's it! Whenever I need the language code, I just access it on the request. I really like this solution. It's as simple as it gets, and it's a really nice way to make sure the current request will always use the same value.
The only downside of this approach is that you have to pass the request (or the cached value itself) to all places where you need the value.
If you want to avoid hitting the db for requests that do not need to access the value at all, you can use django.utils.functional.lazy or django.utils.functional.SimpleLazyObject in your middleware. They are not documented as public APIs, so look at the source for details.