Hosting multiple sites within a single Django project

Imagine we are running a successful online store for cat food (let’s call it catfood247.com). Since things are going so well, we would like to expand our business with a second store for dog food, dogfood247.com. Does this mean we’ll have to set up a separate server even though the two stores will be very similar and share a lot of code? Having more servers means higher maintenance & running costs which are obviously things that, if possible, we would like to avoid.

Luckily, Django’s built-in “sites” framework enables us to run two or more websites within a single Django installation. Consider the following project layout:

.
└── petfood
    ├── petfood
    │   ├── settings.py
    ├── catfood
    │   ├── urls.py
    │   ├── views.py
    │   ├── …
    ├── dogfood
    │   ├── urls.py
    │   ├── views.py
    │   ├── …
    ├── manage.py
    └── …

We have three Django apps: petfood, the main app holding the global settings.py file every Django project needs to have; and catfood as well as dogfood , the two apps representing the actual sites to be served.

In addition to the app directories, we need to create two instances of Django’s Site model. The Site model is a simple Django model meant to logically represent a website by its domain & user-defined name. In our case, the following two Site objects are needed:

Site(name='catfood', domain='catfood247.com')
Site(name='dogfood', domain='dogfood247.com')

In other words, each site is represented by a Django app directory (including it’s own URLconf) and a corresponding Site object in Django’s database.

The only thing left to do is create a mechanism for determining the right URLconf on a per-request basis. Django’s documentation gives us a valuable hint at how to achieve what we want:

When a user requests a page from your Django-powered site, this is the algorithm the system follows to determine which Python code to execute:

1. Django determines the root URLconf module to use. Ordinarily, this is the value of the ROOT_URLCONF setting, but if the incoming HttpRequest object has a urlconf attribute (set by middleware), its value will be used in place of the ROOT_URLCONF setting.
2. (…)

How Django processes a request

In other words, Django makes it possible to set a URLconf for each separate request, thereby allowing us to differentiate between to or more Sites and their respective URLconf. Let’s go ahead and define a new middleware that sets the request.urlconf attribute based on the requested site’s name (e.g. catfood):

class SetURLConfMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        request.urlconf = f"{request.site.name}.urls"
        response = self.get_response(request)
        return response

Don’t forget to add this middleware to the MIDDLEWARE list in your settings.py in order to activate it. Also, make sure to insert it after Django’s CurrentSiteMiddleware so that it has access to the request.site attribute.

And that’s all there is to it! From now on, whenever Django receives a request, it first determines the site the request should be forwarded to based on the request’s domain. This simple method makes it possible to support an arbitray number of sites within a single Django setup.

Note for Heroku users: Don’t forget to register each of your domains with your Heroku app!