Creating a dynamic multisite using Django

Posted on: August 13, 2011Author: Rob

Middleware

Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input and/or output.

But what does that mean? It means we can play around with Django’s request and repsonse objects independent of our views.

You can think of your application like an amusement park and your views are the rides. Before you can enter a ride, you will have to pass certain areas. First of all you need to enter the park (e.g. buy a ticket), once inside the park you have to wait in line before you can enter the ride. But hold on, are you tall enough for the ride? Now it is time to strap you in, after which the ride can start. After the ride you need to unstrap. Did you behave yourself during the ride? No, oh than we may need to kick you out of the park. Oh you did behave yourself, well then, go on and find your way to the next ride (or get in line for the same ride again of course).

The same happens with requests when they enter the middleware framework. Django provides some built in components which perform all kinds of maintenance for you. They ensure security is in order (do you have a ticket?), that you made a valid request (are you tall enough?), etcetera. These components are two-way, so when a request comes in, you’ll pass all components in order and when a response is generated in the view, all components will be passed in reverse order again. During both phases components can perform checks, make changes and even hyjack the responses in case something goes wrong.

We will create a custom middleware component which checks the incoming request and figures out for which domain the request is being made. Next it will check if that domain is available within the sites framework. Once the site is found, it will set the correct SITE_ID and continue on in the chain of components. All this will be done before the request even hits the view, so our views don’t even need to be aware of our component fiddling with the system.

Adding our custom middleware component

Let’s add a new file to our project: middleware.py, which will contain our new custom middleware component. Your directory structure should now look something like this:
Directory Structure Middleware

Inside the new middleware.py file we’ll create our middleware component class. Fortunately a middleware component is really easy to create. Just create a regular class and add functions for each stage you want to do stuff with. The various stages are:

  • process_request
  • process_view
  • process_template_response
  • process_response
  • process_exception

We’ll only use process_request for our purposes, because that hook will be called first, the others aren’t necessary. If you want to know more about them, you can find extensive documentation in the Django manual. It is good to realize, though, that all process_* functions are optional for every middleware you create. Django will only call them when they are present in your middleware.

Let’s write our middleware class. Open middleware.py and add the following lines:

from django import http
from django.conf import settings
from django.contrib.sites.models import Site

These lines ensure we have alle the components we need. Django.http is used for creating request and response objects. Django.conf.settings is our own settings.py file, which was created and edited during the Django tutorial. Finally we see django.contrib.sites.models.Site, which is of course the sites framework model we are going to use.

Now it’s time to create our class. We’ll call it MultiSiteMiddleware for now, but feel free to name it any way you want. Inside we define only one function: process_request. This function will be called by the middleware framework at the beginning of every request made. The request is an HttpRequest object which we will need to determine which host is being accessed.

class MultiSiteMiddleware(object):
    def process_request(self, request):

Process_request can return two values, either None in case you simply want the next component in line to start processing the request, or a HttpResonse object if you want processing to stop and Django will return the response immediately without calling any other components.

Our processing now becomes very easy, first we request the host being accessed using the request.META collection. This collection contains all kinds of information about the request, but we only need the HTTP_HOST value.

        host = request.META['HTTP_HOST']

Next we query our Site object to see if the host exists in our sites table. If we find a matching host we set our settings.SITE_ID to the id of the site found and clear the site cache. Clearing the cache ensure we reset any previous SITE_IDs we may have used/found earlier.

        site = Site.objects.get(domain=host)
        settings.SITE_ID = site.id
        Site.objects.clear_cache()
        return

If we don’t find a matching host, we simply redirect to our main domain and we’re done. The Site model throws a DoesNotExist exception when no matching site is found, so if we catch that exception and redirect, we are done:

        try:
            site = Site.objects.get(domain=host)
            settings.SITE_ID = site.id
            Site.objects.clear_cache()
            return
        except Site.DoesNotExist:
            return http.HttpResponseRedirect('http://www.my-awesome.site.com')

Now that we have created our middleware, it is time we start using it. Open your settings.py file and go to the MIDDLEWARE_CLASSES:

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    # ...
)

This section contains all the middleware components being used by your project. You are free to use as many as you like (even zero), but by default Django enables the 5 components shown above. You can find out what they do by checking out the Django manual. For now it is important that we know that they are being processed in order when a request comes in for a view and in reverse order when the response is returned from the view.

We want our SITE_ID to be set as early as possible, so all following components know which site we are dealing with. All we need to do for that is adding our custom middleware at the top of the list:

MIDDLEWARE_CLASSES = (
    'middleware.MultiSiteMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ...
)

That’s it! You can now dynamically add sites in any way you want to and they will immediately be available for your users to access. How you want to add the sites is up to you. You can do it yourself using the admin panel, or you can write some custom logic which creates the sites on the fly. Be creative and think about how you can use dynamic multisites in your project.

I’ve deliberately kept MultiSiteMiddleware as simple as possible for this tutorial. Obviously a couple of enhancements can be thought of immediately. How about requests made with a port number attached (www.my-awesome-site.com:80), how would you deal with that? What are the performance implications? Think about it and extend this middleware to fit your needs.

The code

Let’s wrap up this code as well:

middleware.py

from django import http
from django.conf import settings
from django.contrib.sites.models import Site

class MultiSiteMiddleware(object):
    def process_request(self, request):
        host = request.META['HTTP_HOST']
        try:
            site = Site.objects.get(domain=host)
            settings.SITE_ID = site.id
            Site.objects.clear_cache()
            return
        except Site.DoesNotExist:
            return http.HttpResponseRedirect('http://www.my-awesome.site.com')

settings.py

MIDDLEWARE_CLASSES = (
    'middleware.MultiSiteMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
)

Finally

This tutorial has shown you a way to enable dynamic multisites using the Django framework. Adding this capability to the framework, frees you up to explore fun ways to use dynamic sites without the need to write enormous amounts of code. I really like the way Django handles extending the platform, which allows for this easy implementation. Although I’m quite sure other platforms support similar solutions as well.

Pages: 1 2 3

  • http://dyve.net Dylan Verheul

    Very nice tutorial. Do you think it is safe to overwrite settings.SITE_ID? What about multithreading servers?

    • Rob

      I think Django’s thread safety should prevent any serious issues, although I must admit I haven’t tested that yet. It is a valid concern though, I’ll see if I can think of an easy way to test this :).

      • RobM

        Hi Rob,

        Did you ever figure out a way to test if indeed Django prevents serious problems? Seems there hasn’t been a follow up on this for a while.

    • Rob

      It seems like this solution indeed faces some threading issues. However, the general direction is valid, we just need some extra measures. Another example implementation can be found here:

      https://bitbucket.org/wkornewald/djangotoolbox/src/535feb981c50/djangotoolbox/sites/dynamicsite.py

  • PhilipR

    Hi Rob

    This example was exactly what I was looking for, and it’s well written. Did you ever test the thread safety issue?

    • http://www.two-legs.com/ Rob Been

      Hi Philip,

      Because we are directly updating the settings in the middleware this solution unfortunatly does have some threading issues.

      However, I’ve found another example implementation which uses adds another middleware to avoid the threading issues:

      https://github.com/shestera/django-multisite

      Hope this helps :D