For a personal project (details forthcoming), I've been trying to have separate subdomains for accounts and allow some of these accounts to have CNAME support. So, if I have an account cwilliams, my data should be available at cwilliams.myproject.com. I could also add myproject.christopher-williams.net as a CNAME and get the same view.
The implementation is fairly straightforward. I ended up taking django-subdomains, removing some functionality that I didn't need and adding the CNAME handling.
To use this middleware:
- Put middleware.py (see below) somewhere in your project. I have a Django app called core for things like this.
- Add a CNAME field to a model of your choosing (a user profile might be appropriate), preferably as a CharField. URLField will not work.
- Add a SlugField to the same model and wire that up like you would any other slug.
- Add a new subdomain_urls.py and put the URLconf that should be used for your subdomains/CNAMEs.
- Add SUBDOMAIN_URLCONF='yourproject.subdomain_urls' to settings.py.
- Add core.middleware.SubdomainMiddleware to MIDDLEWARE_CLASSES in settings.py (bottom of the list should work for most usages).
- Create some fake DNS entries in your hosts file (see below).
- Set up your slug/CNAME entries created in your model (i.e. for slugs, test1, test2. For CNAMEs, djangotest.christopher-williams.net, etc.)
- Open your fake subdomains in your browser.
Any hostname that doesn't have a corresponding slug/CNAME will be routed through your ROOT_URLCONF. Otherwise, you should see what's in your SUBDOMAIN_URLCONF.
The Code
/etc/hosts:
[...]
# Fake DNS entries for testing the subdomain middleware
127.0.0.1 test1.django.local
127.0.0.1 test2.django.local
127.0.0.1 djangotest.christopher-williams.net
127.0.0.1 django.local
middleware.py:
from django.utils.cache import patch_vary_headers
from django.conf import settings
from django.db.models import Q
from account.models import Account
class SubdomainMiddleware:
def process_request(self, request):
# Strip off the port, mostly useful for development environments
fqdn = request.get_host().split(':')[0]
# Break up the domain into parts and get the subdomain slug
domain_parts = fqdn.split('.')
if len(domain_parts) > 2:
subdomain = domain_parts[0]
if subdomain.lower() == 'www':
subdomain = None
else:
subdomain = None
try:
request.account = Account.objects.get(
Q(cname=fqdn) |
Q(slug=subdomain)
)
except Account.DoesNotExist:
pass
else:
request.urlconf = settings.SUBDOMAIN_URLCONF
def process_response(self, request, response):
patch_vary_headers(response, ('Host',))
return response