While Django is a great framework for building websites and web applications, it’s often considered as “too big” for fulfilling simple needs, such as mostly static content or single-page sites. While alternatives like static website generators (such as Jekyll or Hyde) or microframeworks (such as Flask) indeed are more lightweight in their default setup, Django isn’t neccessarily too big for the task. If you’re familiar with Django already, it may very well be a better option than having to learn another tool.
This tutorial describes how to set up Django to server a mostly static content site, while still taking advantage of templating system and making it easy to add interaction such as contact or subscribe forms.
Our setup will be a pure-Python system, not dependent on any other software, compatible with both Heroku and the usual Linux server/VPS setup.
We’ll be using virtualenv to keep all the dependencies in one place and avoid polluting our system with them. We’ll also be using virtualenvwrapper because it makes using virtualenv so much easier.
Let’s get started.
Let’s make a new virtual environment for our project and install Django in it:
$ mkvirtualenv --no-site-packages mysite $ workon mysite $ pip install django
Let’s prepare a repository (I use git here, but any works well) and create a new Django project:
$ django-admin.py startproject mysite
So far, standard Django stuff. Let’s open up
mysite/mysite/settings.py and make things more interesting. For our purposes, this is the complete
import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) SECRET_KEY = '...' # must be unique for your project DEBUG = TEMPLATE_DEBUG = False ALLOWED_HOSTS = ['mysite.com'] # must match your site domains INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', ) MIDDLEWARE_CLASSES = ( 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' ROOT_URLCONF = 'staticsite.urls' WSGI_APPLICATION = 'staticsite.wsgi.application' STATIC_URL = '/static/' STATIC_ROOT = 'staticfiles'
We won’t go over what each of the settings variables does, but do look them all up, it’s only a handful anyways.
Notice there’s no database, no session, and no authentication framework! We don’t need any of them, so we simply stripped them out. We did keep the messages framework and CSRF and clickjacking protection, as they will be useful if we have contact form on the site.
By the way, you may want to start with
True, as it will make it easier to spot errors while you set things up. Make sure you switch it back to False before deploy, though!
Next, open up
mysite/mysite/urls.py and get rid of the admin-related stuff there (put there by default).
After this, you should have a trimmed down Django site that does absolutely nothing. Test it out:
$ cd mysite $ python manage.py runserver
Everything seems to be in order, let’s commit that as the initial commit in the repository:
$ git add manage.py mysite/*.py $ git commit -m 'initial commit'
The static pages app
Now that we’ve got Django up and running, let’s create a Django app that will actually serve our pages:
$ python manage.py startapp pages
We don’t actually need admin support and won’t write any tests for this, so we can get rid of the created-by default unneccessary files:
$ rm pages/admin.py pages/tests.py
We don’t need anything in pages/models.py but Django does require it in order to consider the package to be an app, so the file must be present. It can be blank though:
$ rm pages/models.py $ touch pages/models.py
We need to add the app to
INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', 'pages', )
And make it responsible for all of the URLs that Django will serve, so now
mysite/urls.py will look like:
from django.conf.urls import patterns, include, url urlpatterns = patterns('', url(r'', include('pages.urls')) )
Finally, let’s add some content. We’ll define the URLs and templates directly in
pages/urls.py, and we’ll be making use of
TemplateView so we don’t need to add any custom code.
Let’s create it with the following content to start with:
from django.conf.urls import patterns, url from django.views.generic import TemplateView urlpatterns = patterns('', url('^$', TemplateView.as_view(template_name='index.html')), )
Now we just need to add the content. The templates will be looked up in
pages/templates directory by default, so let’s create it and add
After verifying it works with runserver, let’s commit those changes as well:
$ git add mysite/settings.py mysite/urls.py pages/*.py pages/templates/index.html $ git commit -m 'add static pages app'
The static assets will be looked up in
pages/static directory, this is the default Django staticfiles behaviour.
The built-in runserver is fine and well, but it’s not designed for actual production use. A popular Python web application server is gunicorn, and that’s what we’ll be using here:
$ pip install gunicorn
Once installed, you can run it with:
$ gunicorn mysite.wsgi:application
Oh noez! Gunicorn doesn’t serve the static assets by default! This is because Django does a terrible job of serving them and doesn’t even pretend it’s usable for non-development (non-runserver) use. We must find another solution.
A nice solution is dj-static, a WSGI app that can wrap Django and take over serving the static contents:
$ pip install dj-static
To use it, we just need to modify mysite/wsgi.py slightly:
import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") from django.core.wsgi import get_wsgi_application from dj_static import Cling application = Cling(get_wsgi_application())
Start gunicorn again and all your static files should be accessible.
Let’s commit these changes as well:
$ git add mysite/wsgi.py $ git commit -m 'use dj-static for serving static files'
And we’re done!
To wrap it up, we’ll also add
requirements.txt tracking required Python packages (the versions pinned here are latest as of this writing, yours will probably change, see the output of
Django==1.6.2 dj-static==0.0.5 gunicorn==18.0 static==1.0.2
If you’re planning to use Heroku, make sure you add
requirements.txt to the root of your repository, not in the
$ git add requirements.txt $ git commit -m 'specifying dependencies in requirements.txt'
Bonus: deploying on Heroku
Since our app doesn’t rely on anything besides Python packages specified in
requirements.txt, and doesn’t even need a database, it’s trivial to deploy it to Heroku.
First, let’s create a Procfile that will tell Heroku how to run our app:
web: cd staticsite && gunicorn -b 0.0.0.0:$PORT -w 3 staticsite.wsgi:application
The gunicorn option
-w 3 here tells it to run 3 worker processes. The actual number depends on the memory usage of Django, and may be much higher than 3, but I’ve never seen three workers being problematic, so it’s a safe bet.
Now we can create a new Heroku app:
heroku apps:create mysite
The only thing remaining is to push to the new app:
git push heroku master:master
If you’ve managed to read this far, congratulations! In case you don’t want to manually go through all the above steps, I’ve prepared a git repository with the same setup (the only additions are README with install instructions and bundled CSS from purecss.io for the static asset example).
Just clone dobarkod/django-staticsite and start building your static site with Django in seconds.