While surveying the modularity and reusability of code written with Django, I’ve observed that projects usually travel down two divergent paths of evolution. Either they start small and grow into behemoths as developers add feature-after-feature, or they stop growing after a certain point and are packaged into reusable components. Today I propose a third route that weaves between these two extremes and allows you to reuse your existing code by running multiple websites from a single codebase. In essence, I want you to feel like you're developing one site, when in reality you're making multiple. For lack of a better term, I’ll call this process Franchising your Django project.

Why franchise your project?

When my organization began licensing our research-tracking software to multiple clients, each of them wanted to make minor adjustments to various aspects of our software suite. Our initial response to this situation was to branch the original code into several different versions for each site. In this way, when a client wanted minor changes, we would make the modification to their respective code branch. This approach worked fine as long as we weren’t adding any new features to our software. The problem, however, was that we we were. As more clients began licensing our software and as we continued to make improvements to it, the process of syncing those changes between all the different code branches became unmanageable. Despite heroic efforts, our svn-fu was not up to par. Franchising was our answer to this tangled mess.

Defining “Franchise”

The goal was to develop a system capable of sharing common components between multiple sites from a single code-base while keeping it flexible enough for us to make minor changes to each specific site. The critical question that we had to answer was, “What did we need to keep in common between all sites, and what were the things we wanted to change from site to site?” The table below is our attempt at redefining the above question in terms of components common to a Django project.
Django Components in a Franchised Project
Globally-Shared Site-Specific
database x
models x
urls x x
views x x
settings x x
templates x x
From this table, three important pieces of information stand out. One, most Django components - the settings, views, urls, and templates - will have both a globally shared aspect and a site-specific aspect. Two, all sites, despite their differences in templates and views, will have a globally shared model schema. Why? because this allows us to develop our Django project as if we were running only one site. Finally, each site will have its own unique database since each site will have its own unique data. With this in mind, a typical directory layout of a “franchised” Django project might look something like this: Franchised Django Project - Directory Layout
folder structure
In the top level directory sits all of the globally shared settings, applications, and urls. Under that, a “site_overloads” folder will contain all of the site specific components such as databases, urls, and templates.

Implementing a Franchised Django Site

Now the trickiest part about franchising your django project is getting the globally shared components and the site-specific components working seamlessly with each other. Inherent within this problem is the challenge of figuring out where to route users once they reach one of your sites. Going along with our research-tracker example, imagine that a user has typed “http://research.nasa.gov/” into their browser. If it was possible to dynamically configure Django for a specific site’s settings before responding, we could essentially run many sites from a single codebase by switching between the different configurations. Luckily for us, Apache and most web-servers allow you to dynamically set environment variables depending on the url of the incoming request. Here’s how you do it within apache’s /etc/apache2/httpd.conf file /etc/apache2/httpd.conf
<VirtualHost *:80>
        SetHandler python-program
        PythonHandler django.core.handlers.modpython
        SetEnv DJANGO_SETTINGS_MODULE settings
        SetEnv OVERLOAD_SITE nasa
        PythonPath "['/var/www/data/research_tracker'] + sys.path"
        ServerName research.nasa.com
</VirtualHost>
Using the SetEnv directive, we set a variable called “OVERLOAD_SITE” to “nasa”. Now, anytime someone requests a page from “http://research.nasa.com” Apache will automatically set the variable before handing off the request to Django. By appending the following snippet to our top-level settings.py file, we can use the environment variable to dynamically load the desired site-specific settings. globally-shared settings.py
import os
OVERLOAD_SITE = os.environ.get('OVERLOAD_SITE')
OVERLOAD_SITE_MODULE ="site_overloads" + "." + OVERLOAD_SITE
exec "from %s.settings import *" % (OVERLOAD_SITE_MODULE)
The os.environ.get() function in the above code extracts the value of our desired variable, “OVERLOAD_SITE”, from the operating environment. Using this value, it determines the correct import path to the site-specific settings. Finally, with the exec() function, it imports the site specific settings. To reiterate our general approach: 1. Use Apache to pass off requests to Django, but before doing so, set an environment variable dependent upon the request url. 2. Read this environment variable from within the project’s globally shared settings.py and determine the desired site. 3. From within the globally shared settings.py, import the desired site-specific settings module.

Configuring the site-specific settings to override global settings

Django Components in a Franchised Project
Globally-Shared Site-Specific
database x
models x
urls x x
views x x
settings x x
templates x x
Earlier, we defined the boundaries between globally shared components and site-specific components in a franchised Django project(see above table). With our dynamic configuration setup in place, we can now force site-specific components such as the database, urls, views, and templates to override the default global components. The key to doing so is defining our intentions in the site-specific settings.py. From our NASA research-tracker example, that file would be site_overloads/nasa/settings.py

Setting The Site-Specific Database

By adding these few lines, we can specify to Django the exact database to use for our NASA site. site_overloads/nasa/settings.py
# Overload Default database
DATABASE_NAME = "site_overloads/nasa/",'database.sqlite3'
Overriding Global Templates with Site-Specific Templates To make sure that templates in the site_overloads directory override the globally shared templates, we have to modify both the globally-shared and site-specific settings.py files like so: site_overloads/nasa/settings.py
# Overload Default TEMPLATE_DIRS
SITE_TEMPLATE_DIRS = [
    "site_overloads/nasa/templates",
]
globally-shared settings.py
# Prepend a list of site-specific template directories to the TEMPLATE_DIRS
if SITE_TEMPLATE_DIRS:
    TEMPLATE_DIRS = SITE_TEMPLATE_DIRS + TEMPLATE_DIRS
As a result of these settings, when rendering templates, files in the site_overloads/nasa/templates folder will always be chosen over files in the globally shared templates folder, even if they share the same name.

Adding Site-Specific URLs

In the globally-shared u{urls.py} add these few lines of code to dynamically import the correct site-specific urls: urls.py
# import site specific urls
from django.conf import settings
if hasattr(settings, "OVERLOAD_SITE_MODULE"):
   exec "from %s import urls as site_urls" % (settings.OVERLOAD_SITE_MODULE)
   urlpatterns = site_urls + urlpatterns
With this in place you can now add url patterns as normal under your site-specific urls.py.

Testing the setup

After making all of those adjustments, you’ll most likely want to try out your new setup without having to startup Apache. A good way to test our solution is to manually set the environment variable before running “manage.py runserver” or “manage.py shell”. Type the following on the commandline to run a shell using the site-specific setup. Once inside the Django shell we can poke around at the various site-specific settings and verify that they are what we expect:
OVERLOAD_SITE=nasa; manage.py shell
> from django.conf import settings
> print( settings.OVERLOAD_SITE )
nasa

Summary

In this article, I outlined how to franchise your Django project. As our main approach, we used Apache’s ability to set environment variables to dynamically configure Django before responding to any requests. Because of this, we could dynamically route users to site-specific views, urls, templates, and databases depending on the incoming request url. Franchising allows you to run many Django sites from a single code base. It’s ideal for those times when you want to reuse code that you have written for one site on another project. There is no need to maintain several branches of slightly different code, and as a result, improvements made on one site will simultaneously apply to all sites.