I'm converting an e-commerce site to be region (e.g., US, EU) specific, so it will basically feel like a different site to visitors based on the content they'll see, even though it's actually one site (for many reasons). Most of the paths on my site will become region-specific, by prefixing with the region at the beginning of the path e.g., '/us/' (I could convert all however if it made it dramatically easier).
My plan:
Middleware identifies the region based on 1) request path, 2) session, or 3) guessing based on the IP address in that order, and it's set on the request object. Also, once they use a regional path, it gets stored as a session value. In this way the region context is carried across the URLs which are not region-specific.
The URL patterns which are region-specific have to be updated to match region, even though I've already detected the region in the middleware, since the logic was more complicated than just path. Nevertheless I must make it a parameter and pass into all of my views, for the next reason (reversing). Also, any path which is becoming regional, will have their previous patterns 301 redirecting to their regional paths.
In order to generate links which are region-specific, I have to update many calls to reverse() and {% url %} by adding the region argument. I wish there was some layer here I could customize to dynamically reverse the URLs with knowledge of the request.
My primary question is the best way to handle reversing (the last bullet). It feels like a lot of unnecessary work. I am open to better ways to solve the problem overall.
Updates:
- I ruled out subdomains because they are known to be bad for SEO, of transferring authority. Also, I think subdomains can imply totally different setups whereas for now I will manage this as a single webapp.
- As @RemcoGerlich points out, basically I want to add the automagic behaviors that LocaleMiddleware/i18n_patterns adds both in urlconf and in reversing.
I came up with several ways this could be done (the fourth is a bonus using subdomains). All assume a middleware that detects region and sets it on the request.
Following @RemcoGerlich's tip, mimic how Django handles the internationalization of URLs.
LocaleMiddleware
detects the language and sets the active language on that request (with a thread local variable). Then, that active language is used to form the URLs with i18n_patterns(), which actually returns aLocaleRegexURLResolver
(which subclasses the normal resolver) instead of urls. I believe something similar could be done to support other types of prefixes.A more brute force approach is to again store region not only in the request but again in a thread local variable as Django does for the active language. Update URLs to have a named argument for the region prefix and add to view arguments. Implement a custom reverse to add the region parameter. If inclined to do evil, this could be monkeypatched to avoid touching every single
reverse
andurl
template reference.Use middleware to set the
request.urlconf
based on the region, to override theROOT_URLCONF
. This provides a whole different set of URLs for this request only. Create one new URLconf per region, which add their prefix and then include the base URLconf. No need to capture the region part of the path or mess with view parameters. Reversing the URLs "just works".If you wanted to use subdomains, which I didn't, there's a Django App called
django-hosts
as referenced in this question: Django: Overwrite ROOT_URLCONF with request.urlconf in middleware.For my application, overriding
request.urlconf
with middleware was the simplest and most elegant solution. Here's a fragment from the middleware:I created one new URLconf per region, but they are DRY one-liners:
These referenced a template that combined both the URLs I wanted to be regional with those I wanted to leave "bare":
And finally a redirect view for backward compatibility:
Make sure to update caching to include region. ;)