According to the Django project homepage, it is a “high-level Python Web framework that encourages rapid development and clean, pragmatic design”.

This tutorial will show how to build a minimal Django project from scratch on DotCloud, storing data in a PostgreSQL database.

All the code presented here is also available on GitHub, at http://github.com/jpetazzo/django; moreover, you can read the whole instructions in two original ways:

  • using GitHub’s awesome compare view – click on each individual commit to see detailed explanations for each step;
  • or, if you prefer text mode (or offline inspection), fallback on git log --patch --reverse begin..end.

Directory Structure

To experiment with this tutorial, you can clone the corresponding GitHub repository with git clone git://github.com/jpetazzo/django, or do it step by step, by creating and modifying the files as instructed at each step.

If you chose to do it manually, the final directory structure should look like this:

.
├── dotcloud.yml
├── hellodjango/
│   ├── __init__.py
│   ├── manage.py
│   ├── settings.py
│   ├── someapp/
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── tests.py
│   │   └── views.py
│   ├── someotherapp/
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── tests.py
│   │   └── views.py
│   └── urls.py
├── mkadmin.py
├── nginx.conf
├── postinstall*
├── requirements.txt
└── wsgi.py

Note

This tutorial does not create someapp and someotherapp; they are shown here so you can see where your custom apps should be added.

DotCloud Build File

The DotCloud Build File, dotcloud.yml, describes our stack.

We will start with a single “python” service (we will add the database later). This service allows us to expose a WSGI-compliant web application. Django can do WSGI out of the box, so that’s perfect for us.

dotcloud.yml:

www:
  type: python

The role and syntax of the DotCloud Build File is explained in further detail in the documentation, at http://docs.dotcloud.com/guides/build-file/.

Specifying Requirements

A lot of Python projects use a requirements.txt file to list their dependencies. DotCloud detects this file, and if it exists, pip will be used to install the dependencies.

In our case, we just need to add Django to this file.

requirements.txt:

Django

pip is able to install code from PyPI (just like easy_install); but it can also install code from repositories like Git or Mercurial, as long as they contain a setup.py file. This is very convenient to install new versions of packages automatically without having to publish them on PyPI at each release.

See http://www.pip-installer.org/en/latest/requirement-format.html for details about pip and the format of requirements.txt.

Django Basic Files

Let’s pretend that our Django project is called hellodjango. We will add the essential Django files to our project. Actually, those files did not come out of nowhere: we just ran django-admin.py startproject hellodjango to generate them!

Note

The rest of the tutorial assumes that your project is in the hellodjango directory. If you’re following those instructions to run your existing Django project on DotCloud, just replace hellodjango with the real name of your project directory, of course.

The files generated by startproject are hellodjango/__init__.py, hellodjango/manage.py, hellodjango/settings.py, and hellodjango/urls.py.

wsgi.py

The wsgi.py file will bridge between the python service and our Django app.

We need two things here:

  • inject the DJANGO_SETTINGS_MODULE variable into the environment, pointing to our project settings module;
  • setup the application callable, since that is what the DotCloud service will be looking for.

wsgi.py:

import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'hellodjango.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

We can now push our application, by running dotcloud push djangotutorial (you can of course use any application name you like). A Python service will be created, the code will be deployed, and the URL of the service will be shown at the end of the build. If you go to this URL, you will see the plain and boring Django page, typical of the “just started” project.

Add PostgreSQL Database

It’s time to add a database to our project! The first step is to tell DotCloud that we want a PostgreSQL server to be added to our application. Just edit the Build File again.

dotcloud.yml:

www:
  type: python
db:
  type: postgresql

Note that we called our database db, but it could have been anything else, really.

If you dotcloud push again, you will see that the database service will be created (DotCloud will notice that we added a section to the Build File).

Add Database Credentials to settings.py

Now, we need to edit settings.py to specify the host, port, user, and password to connect to our database. When you deploy your application, these parameters are stored in the DotCloud Environment File. This allows you to repeat the deployment of your application (e.g. for staging purposes) without having to manually copy-paste the parameters into your settings each time.

If you don’t want to use the Environment File, you can retrieve the same information with dotcloud info hellodjango.db.

The Environment File is a JSON file holding a lot of information about our stack. It contains (among other things) our database connection parameters. We will load this file, and use those parameters in Django’s settings.

See http://docs.dotcloud.com/guides/environment/ for more details about the Environment File.

hellodjango/settings.py:

# Django settings for hellodjango project.

import json
with open('/home/dotcloud/environment.json') as f:
  env = json.load(f)

DEBUG = True
TEMPLATE_DEBUG = DEBUG
# …
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'template1',
        'USER': env['DOTCLOUD_DB_SQL_LOGIN'],
        'PASSWORD': env['DOTCLOUD_DB_SQL_PASSWORD'],
        'HOST': env['DOTCLOUD_DB_SQL_HOST'],
        'PORT': int(env['DOTCLOUD_DB_SQL_PORT']),
    }
}
# …

Note

We decided to use the template1 database here. This was made to simplify the configuration process. If you want to use another database, you will have to create it manually, or to add some extra commands to the postinstall script shown in next sections.

Django Admin Site

We will now activate the Django administration application. Nothing is specific to DotCloud here: we just uncomment the relevant lines of code in settings.py and urls.py.

hellodjango/settings.py:

# …
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)
# …

hellodjango/urls.py:

from django.conf.urls.defaults import patterns, include, url

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'hellodjango.views.home', name='home'),
    # url(r'^hellodjango/', include('hellodjango.foo.urls')),

    # Uncomment the admin/doc line below to enable admin documentation:
    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # Uncomment the next line to enable the admin:
    url(r'^admin/', include(admin.site.urls)),
)

If we push our application now, we can go to the /admin URL, but since we didn’t call syncdb yet, the database structure doesn’t exist, and Django will refuse to do anything useful for us.

Automatically Call syncdb

To make sure that the database structure is properly created, we want to call manage.py syncdb automatically each time we push our code. On the first push, this will create the Django tables; later, it will create new tables that might be required by new models you will define.

To make that happen, we create a postinstall script. It is called automatically at the end of each push operation.

postinstall:

#!/bin/sh
python hellodjango/manage.py syncdb --noinput

A few remarks:

  • this is a shell script (hence the #!/bin/sh shebang at the beginning), but you can also use a Python script if you like;
  • by default, syncdb will interactively prompt you to create a Django superuser in the database, but we cannot interact with the terminal during the push process, so we disable this thanks to --noinput.

If you push the code at this point, hitting the /admin URL will display the login form, but we don’t have a valid user yet, and the login form won’t have the usual Django CSS since we didn’t take care about the static assets yet.

Create Django Superuser

Since the syncdb command was run non-interactively, it did not prompt us to create a superuser, and therefore, we don’t have a user to login.

To create an admin user automatically, we will write a simple Python script that will use Django’s environment, load the authentication models, create a User object, set a password, and give him superuser privileges.

The user login will be admin, and its password will be password. Note that if the user already exists, it won’t be touched. However, if it does not exist, it will be re-created. If you don’t like this admin user, you should not delete it (it would be re-added each time you push your code) but just remove its privileges and reset its password, for instance.

mkadmin.py:

#!/usr/bin/env python
from wsgi import *
from django.contrib.auth.models import User
u, created = User.objects.get_or_create(username='admin')
if created:
    u.set_password('password')
    u.is_superuser = True
    u.is_staff = True
    u.save()

postinstall:

#!/bin/sh
python hellodjango/manage.py syncdb --noinput
python mkadmin.py

At this point, if we push the code, we will be able to login, but we still lack the CSS that will make the admin site look nicer.

Handle Static and Media Assets

We still lack the CSS required to make our admin interface look nice. We need to do three things here.

First, we will edit settings.py to specify STATIC_ROOT, STATIC_URL, MEDIA_ROOT, and MEDIA_URL. Note that we decided to put those files in /home/dotcloud/data. By convention, the data directory will persist across pushes. This is important: while you could store static assets in /home/dotcloud/current (or one of its subdirectories), you probably don’t want to store media (user uploaded files…) in current or code, because those directories are wiped out at each push.

hellodjango/settings.py:

# …
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = '/home/dotcloud/data/media/'

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = '/media/'

# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = '/home/dotcloud/data/static/'

# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'

# URL prefix for admin static files -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/static/admin/'
# …

The next step is to instruct Nginx to map /static and /media to those directories in /home/dotcloud/data. This is done through a Nginx configuration snippet. You can do many interesting things with custom Nginx configuration files; http://docs.dotcloud.com/guides/nginx/ gives some details about that.

nginx.conf:

location /media/ { root /home/dotcloud/data ; }
location /static/ { root /home/dotcloud/data ; }

The last step is to add the collectstatic management command to our postinstall script. Before calling it, we create the required directories, just in case.

postinstall:

#!/bin/sh
python hellodjango/manage.py syncdb --noinput
python mkadmin.py
mkdir -p /home/dotcloud/data/media /home/dotcloud/data/static
python hellodjango/manage.py collectstatic --noinput

After pushing this last round of modifications, the CSS for the admin site (and other static assets) will be found correctly, and we have a very basic (but functional) Django project to build on!

- EOF -