Setting up Django on Ubuntu


Written on November 29th, 2011 by Gabriele Pierfederici

Step-by-step instructions for installing Django 1.2.7 on Ubuntu 10.04 LTS (Lucid Lynx).

Useful resources

Prerequisites

Downloads

Store the downloaded files and source code in the ~/src/ directory:

mkdir ~/src && cd ~/src/

Compiling and installing software

Nginx

Minimal configuration for using Nginx as a reverse proxy and for serving static files:

cd ~/src/nginx-0.7.65/

./configure --without-http_access_module --without-http_auth_basic_module --without-http_autoindex_module --without-http_browser_module --without-http_empty_gif_module --without-http_fastcgi_module --without-http_geo_module --without-http_limit_req_module --without-http_limit_zone_module --without-http_map_module --without-http_memcached_module --without-http_referer_module --without-http_rewrite_module --without-http_ssi_module --without-http_userid_module --with-http_ssl_module

sudo make && sudo make install

Create the Nginx init file /etc/init.d/nginx:

#! /bin/sh

### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the nginx web server
# Description:       starts nginx using start-stop-daemon
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/nginx/sbin:/usr/local/nginx
DAEMON=/usr/local/nginx/sbin/nginx
NAME=nginx
DESC=nginx

test -x $DAEMON || exit 0

# Include nginx defaults if available
if [ -f /etc/default/nginx ] ; then
        . /etc/default/nginx
fi

set -e

case "$1" in
  start)
        echo -n "Starting $DESC: "
        start-stop-daemon --start --quiet --pidfile /usr/local/nginx/logs/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
        echo "$NAME."
        ;;
  stop)
        echo -n "Stopping $DESC: "
        start-stop-daemon --stop --quiet --pidfile /usr/local/nginx/logs/$NAME.pid
        echo "$NAME."
        ;;
  restart|force-reload)
        echo -n "Restarting $DESC: "
        start-stop-daemon --stop --quiet --pidfile /usr/local/nginx/logs/$NAME.pid
        sleep 1
        start-stop-daemon --start --quiet --pidfile /usr/local/nginx/log/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
        echo "$NAME."
        ;;
  *)
      N=/etc/init.d/$NAME
      echo "Usage: $N {start|stop|restart|force-reload}" >&2
      exit 1
      ;;
esac

exit 0

Make the file executable and add it to the default run levels:

sudo chmod +x /etc/init.d/nginx

sudo /usr/sbin/update-rc.d -f nginx defaults

Edit the Nginx configuration file /usr/local/nginx/conf/nginx.conf to configure Nginx as a reverse proxy. The Django WSGI server will be listening on localhost at port 8000. Nginx is configured to handle HTTP status codes of 400 and higher if a matching error_page directive exists. Also, any request for /static/*, /robots.txt and /favicon.ico is served statically from /usr/local/nginx/html:

location /static/ {
            root   html;
}

location /robots.txt {
            root   html;
}

location /favicon.ico {
            root   html;
}

location / {
            proxy_pass        http://localhost:8000;
            proxy_set_header  X-Real-IP  $remote_addr;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header  Host $http_host;
            proxy_intercept_errors on;
}

Libevent

Libevent is required for compiling Gevent:

cd ~/src/libevent-2.0.16-stable/

./configure

sudo make && sudo make install

Register Libevent, add the following path to the /etc/ld.so.conf.d/libevent-i386.conf file:

/usr/local/lib

And run:

sudo ldconfig

Greenlet

Greenlet is used by Gevent as an alternative to Python’s built in coroutines:

cd ~/src/greenlet-0.3.1/

sudo python setup.py build && sudo python setup.py install

Gevent

Gevent provides an high-performance WSGI web server and a simple way for monkeypatching the stdlib to make some parts of it cooperative with greenlets:

cd ~/src/gevent-0.13.6/

sudo python setup.py build && sudo python setup.py install

MySQLdb

MySQLdb is required for executing python manage.py syncdb from the command line:

cd ~/src/MySQL-python-1.2.3/

sudo python setup.py build && sudo python setup.py install

PyMySQL

PyMySQL is a pure Python MySQL client and a drop-in replacement for MySQLdb. Being pure Python, PyMySQL can take full advantage of the monkey patched standard library:

cd ~/src/PyMySQL-0.5/

sudo python setup.py build && sudo python setup.py install

Django

Build and install Django using setup.py:

cd ~/src/Django-1.2.7/

sudo python setup.py build && sudo python setup.py install

Copy the Django admin static files to /usr/local/nginx/html/static/:

sudo mkdir /usr/local/nginx/html/static

sudo cp -rv ~/src/Django-1.2.7/django/contrib/admin/media/* /usr/local/nginx/html/static/

Testing installations

Testing MySQLdb

Typing the following commands at the python command prompt (replacing password with the actual password):

import MySQLdb
conn = MySQLdb.connect(host='localhost', user='root', passwd='password', db='mysql')
cursor = conn.cursor()
cursor.execute('SELECT VERSION();')
row = cursor.fetchone()
server_version = row[0]
cursor.close()
conn.close()
print 'server version:', server_version

should produce:

server version: 5.1.41-3ubuntu12.10

Testing PyMySQL

Typing the following commands at the python command prompt (replacing password with the actual password):

import pymysql
pymysql.install_as_MySQLdb()
# same as above
import MySQLdb
conn = MySQLdb.connect(host='localhost', user='root', passwd='password', db='mysql')
cursor = conn.cursor()
cursor.execute('SELECT VERSION();')
row = cursor.fetchone()
server_version = row[0]
cursor.close()
conn.close()
print 'server version:', server_version

should produce:

server version: 5.1.41-3ubuntu12.10

Testing Django

Typing the following commands at the python command prompt:

import django
print 'django version:', django.get_version()

should produce:

django version: 1.2.7

Testing Nginx and Gevent

Start the Nginx server:

sudo /etc/init.d/nginx start

Enter the following commands at the python command prompt:

from gevent import wsgi

def hello_world(environ, start_response):
    start_response('200 OK', [])
    return ['<html>Hello, world!</html>']

wsgi.WSGIServer(('', 8000), hello_world, spawn=None).serve_forever()

Running:

wget -qO- --server-response http://localhost/

should produce:

HTTP/1.1 200 OK
Server: nginx/0.7.65
Date: Fri, 23 Oct 2009 09:05:19 GMT
Content-Type: text/html; charset=ISO-8859-1
Connection: keep-alive
Content-Length: 26
<html>Hello, world!</html>

Wrapping everything up

The Django project must be accessible from within the /usr/local/wsgi/ directory:

sudo mkdir /usr/local/wsgi

Create the /usr/local/wsgi/django-bootstrap.py file:

#!/usr/bin/env python
#
# Copyright: ©2011 Gabriele Pierfederici
# http://www.gabrielepierfederici.com/archive/setting-up-django-on-ubuntu.html
# 
import sys, os, traceback
from optparse import OptionParser
from django.core.signals import got_request_exception
import django.core.handlers.wsgi
from gevent import monkey
from gevent.wsgi import WSGIServer
import pymysql

def exception_printer(sender, **kwargs):
    traceback.print_exc()

def django_app(settings):
    os.environ['DJANGO_SETTINGS_MODULE'] = settings
    got_request_exception.connect(exception_printer)
    return django.core.handlers.wsgi.WSGIHandler()

def wsgi_server(host, port, application):
    return WSGIServer((host, port), application, log=False)

if __name__ == '__main__':
    path = os.path.abspath(os.path.dirname(__file__))
    if path not in sys.path:
        sys.path.append(path)

    parser = OptionParser("usage: %prog [options] arg")
    parser.add_option('--host', type='string', default='localhost', dest='host', help="host name or IP address [default: %default]")
    parser.add_option('--port', type='int', default=8000, dest='port', help="tcp port [default: %default]")
    parser.add_option('--settings', type='string', dest='settings', help="django settings module (mandatory)")
    options = parser.parse_args()[0]
    if  not options.settings:
        print "mandatory option --settings is missing\n"
        parser.print_help()
        exit(-1)
    parser.destroy()

    monkey.patch_all()
    pymysql.install_as_MySQLdb()

    server = wsgi_server(options.host, options.port, django_app(options.settings))
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass

Make the file executable:

sudo chmod +x /usr/local/wsgi/django-bootstrap.py

Creating a bare Django project:

Create the django user and home directory, the project will then be created in the ~/projects/ directory:

sudo useradd -d /home/django -m django && sudo su django

mkdir ~/projects && cd ~/projects/

django-admin.py startproject mysite

exit

Create a symbolic link to /home/django/projects/mysite in /usr/local/wsgi/:

sudo ln -s /home/django/projects/mysite/ /usr/local/wsgi/mysite

Create the init script file /etc/init.d/mysite to start and stop the WSGI server for mysite automatically:

#! /bin/sh

### BEGIN INIT INFO
# Provides:          mysite application instance
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts instance of mysite app
# Description:       starts instance of mysite app using start-stop-daemon
### END INIT INFO

############### EDIT ME ##################
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/wsgi
DAEMON=/usr/local/wsgi/django-bootstrap.py
USER=django    

# Uncomment to enable custom settings for each individual environment (development, staging, production)
# ENV=production

# django project name
NAME=mysite
DESC=mysite

# django settings module
DJANGO_SETTINGS_MODULE=$NAME.settings
if [ -n "$ENV" ] ; then
        DJANGO_SETTINGS_MODULE="$DJANGO_SETTINGS_MODULE.$ENV"
fi

# startup args
DAEMON_OPTS=" --host localhost --port 8000 --settings $DJANGO_SETTINGS_MODULE"

PID_FILE=/var/run/$NAME.pid
############### END EDIT ME ##################

test -x $DAEMON || exit 0

set -e

case "$1" in
  start)
         echo -n "Starting $DESC: "
         start-stop-daemon -c $USER --start --quiet --background --pidfile $PID_FILE --make-pidfile --exec $DAEMON -- $DAEMON_OPTS
         echo "$NAME."
         ;;
  stop)
         echo -n "Stopping $DESC: "
         start-stop-daemon -c $USER --stop --quiet --pidfile $PID_FILE
         echo "$NAME."
         ;;
  restart|force-reload)
         echo -n "Restarting $DESC: "
         start-stop-daemon -c $USER --stop --quiet --pidfile $PID_FILE
         sleep 1
         start-stop-daemon -c $USER --start --quiet --background --pidfile $PID_FILE --make-pidfile --exec $DAEMON -- $DAEMON_OPTS
         echo "$NAME."
        ;;
  *)
    N=/etc/init.d/$NAME
    echo "Usage: $N {start|stop|restart|force-reload}" >&2
    exit 1
    ;;
esac

exit 0

Make the file executable and add it to the default run levels:

sudo chmod +x /etc/init.d/mysite

sudo /usr/sbin/update-rc.d -f mysite defaults

Creating the database:

Create the database and the database user for the project (replace password with the actual password):

mysql -u root -p

CREATE DATABASE mysite CHARACTER SET utf8;

GRANT ALL ON mysite.* TO `mysite`@`localhost` IDENTIFIED BY 'password';

FLUSH PRIVILEGES;

quit

Edit the DATABASES section in /usr/local/wsgi/mysite/settings.py (replace password with the actual password)::

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mysite', 
        'USER': 'mysite',
        'PASSWORD': 'password',
        'HOST': '',
        'PORT': '',
        'default-character-set': 'utf8',
        'OPTIONS' : {
            'init_command': 'SET storage_engine=INNODB',
       } 
   }
}

Enabling the Django admin site:

Edit /usr/local/wsgi/mysite/settings.py replace the ADMIN_MEDIA_PREFIX and INSTALLED_APPS sections with:

ADMIN_MEDIA_PREFIX = '/static/'

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

Edit /usr/local/wsgi/mysite/urls.py:

from django.conf.urls.defaults import *

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

urlpatterns = patterns('',
    # Example:
    # (r'^mysite/', include('mysite.foo.urls')),

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

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

Creating the database tables:

Running python manage.py syncdb should prompt the creation of the superuser:

sudo su django

cd ~/projects/mysite/

python manage.py syncdb

exit

Running the Django project:

sudo /etc/init.d/mysite start