the future is in beta

home about

Deploy django-CMS to Heroku and Amazon S3

06 May 2013

Although I’m a Ruby on Rails lover today I’m going to write about how to deploy a fresh installation of django-CMS to Heroku using Amazon S3 for storing static files.

This is a compilation from these sites:

First of all we’ll create our application directory my_app

$ mkdir my_app && cd my_app

Next, we’ll create our Python’s virtual environment using virtualenv and activate it

$ virtualenv --no-site-packages venv 
$ source venv/bin/activate

Now we can install required Python packages like django itself, django-cms, gunicorn (web server) and some others with pip

$ pip install Django psycopg2 gunicorn dj-database-url django-cms PIL django-storage

We’ll need the requirements.txt file to deploy to Heroku

$ pip freeze > requirements.txt

And create our django project inside the current directory:

$ django-admin.py startproject my_app .

Add these lines to the my_app/settings.py file

# -*- coding: utf-8 -*-
import os
gettext = lambda s: s
PROJECT_PATH = os.path.abspath(os.path.dirname(__file__))

and add these in your INSTALLED_APPS array inside my_app/settings.py

INSTALLED_APPS = (
	# ...
	'cms',
	'mptt',
	'menus',
	'south',
	'sekizai',
	'cms.plugins.file',
	'cms.plugins.flash',
	'cms.plugins.googlemap',
	'cms.plugins.link',
	'cms.plugins.picture',
	'cms.plugins.snippet',
	'cms.plugins.teaser',
	'cms.plugins.text',
	'cms.plugins.video',
	'cms.plugins.twitter',
	'storages',
)

And add these to your MIDDLEWARE_CLASSES and TEMPLATE_CONTEXT_PROCESSORS arrays inside my_app/settings.py

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.doc.XViewMiddleware',
    'django.middleware.common.CommonMiddleware',
    'cms.middleware.page.CurrentPageMiddleware',
    'cms.middleware.user.CurrentUserMiddleware',
    'cms.middleware.toolbar.ToolbarMiddleware',
    'cms.middleware.language.LanguageCookieMiddleware',
)
TEMPLATE_CONTEXT_PROCESSORS = (
    'django.contrib.auth.context_processors.auth',
    'django.core.context_processors.i18n',
    'django.core.context_processors.request',
    'django.core.context_processors.media',
    'django.core.context_processors.static',
    'cms.context_processors.media',
    'sekizai.context_processors.sekizai',
)

Now we’ll configure our templates. First, create the directory where we’ll put our django templates

$ mkdir templates

and create two empty files:

$ touch templates/template_1.html
$ touch templates/template_2.html

in order two access these templates we have to put them in our settings.py file and set up available languages

TEMPLATE_DIRS = (
    # The docs say it should be absolute path: PROJECT_PATH is precisely one.
    # Life is wonderful!
    os.path.join(PROJECT_PATH, "templates"),
)

CMS_TEMPLATES = (
    ('template_1.html', 'Template One'),
    ('template_2.html', 'Template Two'),
)
LANGUAGES = [
    ('en', 'English'),
]

Now let’s configure our my_app/urls.py. First, uncomment this line

admin.autodiscover()

And add these lines

urlpatterns = i18n_patterns('',
    url(r'^admin/', include(admin.site.urls)),
    url(r'^', include('cms.urls')),
)

if settings.DEBUG:
    urlpatterns = patterns('',
    url(r'^media/(?P<path>.*)$', 'django.views.static.serve',
        {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
    url(r'', include('django.contrib.staticfiles.urls')),
) + urlpatterns

Now let’s create the base layout inside templates directory templates/base.html


{% load cms_tags sekizai_tags %}
<html>
  <head>
      {% render_block "css" %}
  </head>
  <body>
      {% cms_toolbar %}
      {% placeholder base_content %}
      {% block base_content %}{\% endblock %}
      {% render_block "js" %}
  </body>
</html>

And we can edit the templates/template_1.html template


{% extends "base.html" %}
{% load cms_tags %}

{% block base_content %}
  {% placeholder template_1_content %}
{% endblock %}

If you just did a fresh install of django projects run these commands to set up your database

$ python manage.py syncdb --all
$ python manage.py migrate --fake

If you want to fire up a webserver to test your local set up you can run

$ python manage.py runserver

Now you can open a browser window and go to http://localhost:8000/

In order to deploy to Heroku we’ll have to setup the database in the my_app/settings.py

# Parse database configuration from $DATABASE_URL
import dj_database_url
DATABASES['default'] =  dj_database_url.config()

# Honor the 'X-Forwarded-Proto' header for request.is_secure()
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

Add a Procfile file in your project root

web: gunicorn my_app.wsgi: 

Finally add your Amazon credentials into settings.py

# Amazon S3 credentials
AWS_ACCESS_KEY_ID       = os.environ['AWS_ACCESS_KEY_ID']
AWS_SECRET_ACCESS_KEY   = os.environ['AWS_SECRET_ACCESS_KEY']

# Amazon S3 URL
AWS_STORAGE_BUCKET_NAME = '<your-bucket-name>'
S3_URL = 'http://%s.s3.amazonaws.com/' % AWS_STORAGE_BUCKET_NAME

# Static files location
STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
STATIC_URL = S3_URL

# Default File storage
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
MEDIA_URL = S3_URL

If you need to use Amazon S3 using http instead of https change this constant to False

AWS_S3_SECURE_URLS = False

You may alse collect your static files and upload them to Amazon S3

$ python manage.py collectstatic

Now you can push to Heroku and set up the database

$ git push heroku master
$ heroku run python manage.py syncdb --all
$ heroku run python manage.py migrate --fake

After adding a new plugin or changing your database you’ll have to execute

$ heroku run python manage.py syncdb
$ heroku run python manage.py migrate