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 href
and action
attributes (for a
and 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 process_response
:
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
Comments
comments powered by Disqus