Saturday 24 November 2012

Django, uwsgi, nginx and virtualenv

Bring on the Pain - just kidding!


There are lots of tutorials online for getting the Python Django web framework and the nginx web server working together. However, some of them skip over specific details of what to do and automatically assume you're a Linux God, able to configure anything hi-tech using only your little finger. Because of this, I've decided to create my own tutorial for doing this with Python 2.7x and a virtualenv.

One part of getting things to work together is the pain of dealing with uWSGI. uWSGI calls itself an "application container server coded in pure C", which deals with the protocols for communicating between the web server (nginx) and your Python framework (Django). What it really is, in fact, is a great WSGI interface written by talented hackers which is, however, rather user-unfriendly.

In addition, most documentation for Django for using uwsgi seems slightly out of date, and uwsgi's own documentation is skimpy and really only hints at what you can do, without practically helping much, especially for those of us whose skills don't include hardcore Linux administration. For example, it took me ages to realize that the options to put in a uwsgi ini file are the same as those displayed when you type "uwsgi --help". Also, I have only a basic knowledge of Unix sockets, and so I got quite stuck.

So I thought, I'll address this! Here is a quick, get-you-up-and-running guide for deploying with Django, uwsgi, nginx and a virtualenv. (This is not a guide to teach you the basics of any of those four things. Only to configure them so you get a web app running!) This guide was tested out in Mint 12 and Ubuntu Server 12.04. It is for a Django project "progress_recorder" that will be stored in my /var/www/py directory - you would replace the name and location with those of your own project.


Steps


1) Install the pip and virtualenv packages for Python
sudo apt-get install python-pip
pip install virtualenv


2) Make a virtualenv for a Python version (and activate)
cd /opt
virtualenv py273
source /opt/py273/bin/activate


3) Install Django into your virtualenv
pip install django
# Check version
python -c "import django; print(django.get_version())"
1.4.2


4) Install uwsgi into your virtualenv
pip install uwsgi
# Check version
/opt/py273/bin/uwsgi --version
1.4.1


5) Install nginx
sudo apt-get install nginx
# Check version
nginx -V
nginx: nginx version: nginx/1.1.19

 # Check it's running
service nginx start
ps aux | grep nginx

- you should see "nginx: master process" in the output somewhere.

6) Make a Django project
-Get the page views/templates all displaying with the test server (python manage.py runserver).
Here is the structure of my project, named "progress_recorder", in a tree outline:

progress_recorder
├── manage.py
└── progress_recorder
    ├── django.ini
    ├── django_wsgi.py
    ├── __init__.py
    ├── settings.py
    ├── templates
    │   ├── entry.html
    │   ├── index.html
    │   └── __init__.py
    ├── urls.py
    └── views
        ├── entry.py
        ├── index.py
        ├── __init__.py


6.a) Set your templates in settings.py      
TEMPLATE_DIRS = (
    '/var/www/py/progress_recorder/progress_recorder/templates',)
       
   
6.b) Set your url patterns in urls.py
urlpatterns = patterns('',
    url(r'^progress_recorder/entry$', 'views.entry.entry'),
    url(r'^progress_recorder$', 'views.index.index'),
)


       
7) Get Django working with uwsgi

7.a) Make a django_wsgi.py file:
vim django_wsgi.py

#!/usr/bin/env python

import os

import django.core.handlers.wsgi

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "progress_recorder.settings")
application = django.core.handlers.wsgi.WSGIHandler()



7.b) Make a uwsgi ini file, named django.ini:
vim django.ini

[uwsgi]
pythonpath = /opt/py273/bin/python
virtualenv = /opt/py273
# set the http port
http = :8000
# change to django project directory
chdir = /var/www/py/progress_recorder/progress_recorder
# load django
module = django_wsgi:application
env = DJANGO_SETTINGS_MODULE=settings


7.c) Launch uwsgi
/opt/py273/bin/uwsgi --ini django.ini

7.d) In your browser, test uwsgi is working with Django
-Go to:
http://localhost:8000/progress_recorder
-you should see a Django template view displayed via uWSGI itself acting as a web server.

7.e) Stop uwsgi
Ctrl + c
Check this has worked with:
ps aux | grep uwsgi
-if you see any uwsgi processes still running, for each process ID:
kill -9 [processid]

8) Get nginx working with uwsgi

8.a) Check the nginx user's user name
head /etc/nginx/nginx.conf
-you should see in there:
user www-data;

8.b) Make a project configuration file for nginx
(uwsgi support is built into nginx)

cd /etc/nginx/sites-enabled
vim progress_recorder.conf

server {
    listen        80;
    server_name     localhost;   
    location / {
        root    /var/www/py/progress_recorder/progress_recorder;
        uwsgi_pass unix:///tmp/progress_recorder.sock;
        uwsgi_modifier1 30;
        include uwsgi_params;
        #autoindex on;
    }
}


-Save the project config file.

8.c) Alter the Django project's uwsgi ini file to use a socket
-remove the http setting, and insert a new line instead.

[uwsgi]
pythonpath = /opt/py273/bin/python
virtualenv = /opt/py273
# New
socket = /tmp/progress_recorder.sock

# change to django project directory
chdir = /var/www/py/progress_recorder/progress_recorder
# load django
module = django_wsgi:application
env = DJANGO_SETTINGS_MODULE=settings



8.d) Start uwsgi as the nginx user, and check that socket is picked up
sudo su - www-data
(Enter your password)
/bin/bash (to get a decent bash shell)
cd [project_directory], i.e.:
cd /var/www/py/progress_recorder/progress_recorder
/opt/py273/bin/uwsgi --ini django.ini


-you should see something like this in the output:
wsgi socket 0 bound to UNIX address /tmp/progress_recorder.sock fd 3

8.e) Reload nginx with the new configurations
service nginx reload

8.f) Check that nginx and uwsgi are playing nicely
-In your browser, go to:
http://localhost/progress_recorder
-you should see the Django template view displayed via both nginx and uWSGI.

Congratulations!