One of the requirements for the new Heart website we've just launched was to allow users to personalise their location to one of 33 radio stations across the country. For various reasons, this meant rewriting all the links on the page, dynamically, depending on the user's location setting.
The easiest place to do this sort of post-processing in Django is in response middleware. So I wrote a quick class that used regexes to grab all the
action attributes (for
form elements respectively - images didn't need localising) and add the relevant locations. Because it was dynamic, I used the ability of
re.sub to call a function to determine the replacement value; and to save on multiple database queries, I saved various things in the instance. So it looked a bit like this:
href = re.compile(r'(href|action)=["\'](.+?)["\']') class LocalisationMiddleware(object): def process_response(self, request, response): self.current_station = get_station(request) self.stations = Station.objects.values_list('slug', flat=True) content = href.sub(self.re_replace, response.content.decode('utf8')) response.content = unicode(content) return response def re_replace(self, matchobj): current_station = self.current_station url = "/%s%s" % (current_station.slug, matchobj.group(2)) return "%s=%s" % (matchobj.group(1), url)
But then, during testing, we started getting some rather odd bug reports. Someone would be happily browsing the London pages, and would suddenly get a link pointing at Essex - which is supposed to be impossible.
We eventually realised what the problem was. Django middleware is instantiated once per process: so several requests were being serviced by the same instance, and the values of the local instance attributes - in particular
self.current_station - were being leaked across requests.
The solution is to use a separate object to contain the current station and the
re_replace method, and instantiate it explicitly in
class LocalisationMiddleware(object): def process_response(self, request, response): url_replacement = UrlReplacement(request) content = href.sub(url_replacement, response.content.decode('utf8')) # etc class UrlReplacement(object): def __init__(self, request): self.current_station = get_station(request) self.stations = Station.objects.values_list('slug', flat=True) def __call__(self, matchobj): # do replacements