Using Django Inside the Tornado Web Server

Inspired by Eric Florenzano’s talk, Using Django in Non-Standard Ways (slides in PDF) at DjangoCon and the announcement of Tornado (tornadoweb.org), I decided to try building a small application using the Django Form library and Django ORM inside Tornado. The process proved easier than I expected, especially with Russell Keith-Magee being able to provide guidance on demand.

Step 1: Create Your Database

While Russell explained that it should be possible to get commands like syncdb running outside of a traditional Django project, it was outside the scope of this small experiment. Instead, I created a Sqlite database manually. For those of you that have forgotten how to do this, this will get you started:

# sqlite3 dev.db

sqlite> CREATE TABLE message (id integer primary key, subject varchar(30), content varchar(250));
sqlite> insert into message values(1, 'subject', 'cool stuff');
sqlite> SELECT * from message;

Step 2: Write Your Application

With the database in place, you can create your application in a single file. We’ll call ours dj_tornado.py.

import sys
import os

import tornado.httpserver
import tornado.ioloop
import tornado.web

# django settings must be called before importing models
from django.conf import settings
settings.configure(DATABASE_ENGINE='sqlite3', DATABASE_NAME='dev.db')

from django import forms
from django.db import models

# Define your Model
class Message(models.Model):
    """
    A Django model class.
    In order to use it you will need to create the database manually.
    
    sqlite> CREATE TABLE message (id integer primary key, subject varchar(30), content varchar(250));
    sqlite> insert into message values(1, 'subject', 'cool stuff');
    sqlite> SELECT * from message;
    """
    
    subject = models.CharField(max_length=30)
    content = models.TextField(max_length=250)
    class Meta:
        app_label = 'dj'
        db_table = 'message'
    def __unicode__(self):
        return self.subject + "--" + self.content

# Define your Form
class DjForm(forms.Form):
    subject = forms.CharField(max_length=100, required=True)
    content = forms.CharField()

# Define the class that will respond to the URL
class ListMessagesHandler(tornado.web.RequestHandler):
    def get(self):
        messages = Message.objects.all()
        self.render("templates/index.html", title="My title",
                    messages=messages)
        
class FormHandler(tornado.web.RequestHandler):
    def get(self):
        form = DjForm()
        self.render("templates/form.html", title="My title", form=form)

    def post(self):
        data = {
            'subject':self.request.arguments['subject'][0],
            'content':self.request.arguments['content'][0],
        }
        form = DjForm(data=data)
        if form.is_valid():
            message = Message(**form.cleaned_data)
            message.save()
            self.redirect('/')
        else:
            self.render("templates/form.html", title="My title", form=form)
  
# map the Urls to the class          
application = tornado.web.Application([
    (r"/", ListMessagesHandler),
    (r"/form/", FormHandler),
])

# Start the server
if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

Step 3: Create Some Templates

As you have seen in the code above I am using 2 simple templates to render the responses. Tornado syntax is very similar to Django/Jinja templates so Django developers should feel right at home here.

<!-- index.html -->
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<ul>
       {% for message in messages %}
         <li>{{ escape(message.subject) }} -- {{ escape(message.content) }}</li>
       {% end %}
     </ul>
<a href="/form/">Add</a>
</body>
</html>
<!-- form.html -->
<html>
<body>
<form action="/form/" method="post">
            {{ form }}
            <input type="submit" value="Submit" />
</form>
</body>
</html>

Step 4: Start Your Server

This is easy: # python dj_tornado.py

Step 5: Profit!

Point your browser to http://127.0.0.1:8888 and enjoy your toy Tornado application.

While this application itself doesn’t do much of anything exciting, it should give you a blueprint for how to use Django in “non-standard” ways and show you how to bring some of your familiar Django tools into different applications.

You can find the code example at http://bitbucket.org/yml/dj_tornado/. I would be glad hear your opinion about this approach.

Comments

  • September 15, 2009 at 10:04 a.m. #
    Alex piped up:

    In my view using Django (or really any other normal WSGI framework) inside of Tornado is rather pointless. The advantage of using Tornado is that it does non blocking IO, however a Django request is going to block for it’s entirety, so you really don’t get anything by using this.

  • September 15, 2009 at 10:20 a.m. #
    Peter Baumgartner mentioned:

    Yeah, the advantage of this in a production environment is debatable. I think it was an experiment in re-using Django bits outside of a traditional Django project more than anything else.

    The ability to get any one of the Python micro-frameworks up and running quickly, only using the Django bits you need, is compelling. The techniques Yann uses here could be applied not only to Lightning, but anyplace you wanted to use Django modules.

  • September 15, 2009 at 10:21 a.m. #
    Alex Ezell commented:

    But he’s not using Django’s request/response objects or it’s middleware handling or anything else related to the HTTP request. He’s only using the models and forms objects which, I think, are wholly unrelated to the asynchronous features that Tornado gives you.

  • September 15, 2009 at 10:38 a.m. #
    Alex commented:

    I agree completely, but in my experience the longest portions of any web request are SQL queries, and those do block :)

  • September 15, 2009 at 11:19 a.m. #
    Eric Florenzano chimed in with:

    Bret Taylor, said this about synchronous DB queries:

    We experimented with different async DB approaches, but settled on
    synchronous at FriendFeed because generally if our DB queries were
    backlogging our requests, our backends couldn’t scale to the load
    anyway. Things that were slow enough were abstracted to separate
    backend services which we fetched asynchronously via the async HTTP
    module.

    http://groups.google.com/group/python-tornado/browse_thread/thread/9a08f5f08cdab108

    Thanks for posting this, by the way, Yann!

  • September 16, 2009 at 12:52 a.m. #
    机票网 piped up:

    I just wish that Django could be async like tornado someday….

  • September 19, 2009 at 3:17 p.m. #
    aaloy piped up:

    In a post in my own blog http://trespams.com/2009/09/14/django-cherrypy-vs-tornado/ I compared Tornado WSGI with CherryPy.

    Actually we’re using ngnix as reverser proxy and to serve media, that connects with one o more CherryPy servers that runs Django via WSGI.

    With this configuration Tornado could server about 16% more connections that CherryPy.

    You could ask me why this configuration, of course. We have found that this gives a lot of freedoom in the way we can configure our servers and scale the applications. With CherryPy the req/s was lower that with Apache+mod_python but it was good enougth and the CPU and Memory required was about 10%.

    Acually we’re testing Tornado as a replacement of CherrPy. Pehaps we could use it as a microframeworks to act as a REST server or XML server, but for normal web applications our plans are to continue with Django.

Got something to say?





Our Products

Premier Real Estate Websites
Our simple and easy real estate CMS. Now open for full developer access.
Trailmapping
Still in development, Trailmapping is a GPS enabled trail guide and trip logger.

Categories

Archives

Elsewhere

What we’ve been up to online

Interested in working with us?
Fill out the form below or contact us at:

PO Box 774441
Steamboat Springs, CO
80477

ph: 970.879.8810
fx:  970.367.8596
info@lincolnloop.com