create blog

go home go home
  1. about
  2. code
  3. wiki
  4. blog

SproutCore, Todos, Django, and Comet in < 100 Lines of Code

This is a continuation of the Django Todos Back-End Tutorial that adds Comet using Roots.

Warning: This is not tested by anyone other than me at the moment, and might prove somewhat bumpy. Feedback is very welcome!

Here is a high-level introduction of how Roots works is available here.

Very little needs to be done to add Comet. There are three main steps on the server side:

  1. Get Roots and Cornelius.
  2. Add views to allow clients to subscribe/connect to events.
  3. Trigger events.

On the client side, there are also three major steps:

  1. Get+set up Pomona.
  2. Subscribe to events.
  3. Receive events.

First Step

You will need at least three terminal windows—one for each server. You might as well open them first.

You will have three directories to work out of:

  • Roots (or Dobby)—not much to do here :)
  • SproutCore Project
  • Django Project

Tip: if you want to be able to open only one TextMate window (or whatever IDE you use), you can place the Django project (and, even, Roots) in a subdirectory of your SproutCore project.

Server-Side

Getting Roots and Cornelius

First, let’s get Roots. In the Roots terminal, navigate to the folder you’d like to keep it, and:

$ git clone git://github.com/ialexi/dobby.git

And now, you can start it (assuming you have the prerequisites):

$ cd dobby
$ python dobby.py
Instating Dolores High Inquisitor of Hogwarts

There may be some deprecation warnings due to issues with Twisted, but they don’t cause problems. (Roots should be ported to node.js soon, and node.js doesn’t have these issues).

Now, get Cornelius. In the Django terminal, navigate to your Django project directory. Then run:

$ git clone git://github.com/ialexi/cornelius.git

Next, you need to add Cornelius to your Django project as an application (making it really easy to import). To do this, edit settings.py and add “cornelius” in the INSTALLED_APPS setting:

	'django.contrib.auth',
	'django.contrib.contenttypes',
	'django.contrib.sessions',
	'django.contrib.sites',
	'todos',
	'cornelius' # <- SEE! :D

Making Views to Make Connections

We can’t trust the client. As such, we do not allow the client to connect itself to any path it wants. Instead, it has to go through our Django web app. Ironically, since we’re just doing a simple application, we are in fact, going to allow the client to connect to whatever it wants (I’ll show how to change this, though).

First, you need an updated urls.py:

from django.conf.urls.defaults import *
 
urlpatterns = patterns('',
	# You don't have to do it this way, but in this example, we match
	# optional ending slashes.
	(r'^tasks/?$', "todos.views.tasks"),
	(r'^task/(?P<taskid>[0-9]+)$', "todos.views.task"),
 
	# See here:
	(r'^tasks/connect/(?P<uid>[^\s]+)$', "todos.views.connect"),
	(r'^tasks/disconnect/(?P<uid>[^\s]+)$', "todos.views.disconnect")
)

This makes tasks/connect/(CLIENT_ID) call our “connect” view, and tasks/disconnect/(CLIENT_ID) call our “disconnect” view.

Now, for the views themselves:

# Import cornelius's Dudley
from cornelius import dudley
 
# Connect
def connect(request, uid):
	paths = json.loads(request.raw_post_data)  # Pomona sends the list of paths to connect to in a JSON array.
	# note: paths is just a plain list of strings; you can loop through them and filter for security.
	dudley.connect(uid, paths)  # Dudley takes a client id and a list of paths. That's it.
 
def disconnect(request, uid):
	paths = json.loads(request.raw_post_data)
	# note: ditto regarding paths being a list of strings.
	dudley.connect(uid, paths)

Triggering Events

Django has nice post-save and post-delete events we can use to trigger events (“updates” in Roots-speak).

Here’s how we use them:

# Comet alerters
from django.db.models.signals import post_save, post_delete
def task_saved(sender, **kwargs):
	try:
		instance = kwargs["instance"]
		dudley.update("tasks", json.dumps(task_to_raw(instance)))
	except:
		pass
 
def task_destroyed(sender, **kwargs):
	try:
		instance = kwargs["instance"]
		data = task_to_raw(instance)
		data["DELETE"] = True
		dudley.update("tasks", json.dumps(data))
	except:
		pass
post_save.connect(task_saved, sender=Task)
post_delete.connect(task_destroyed, sender=Task)

Note how we once again use task_to_raw.

And now, the server-side is done!

Of course, start it with:

$ python manage.py runserver

Client Side

Getting and Setting Up Pomona

In your SproutCore terminal, navigate to your SproutCore project. Then, get Pomona and put it in the “frameworks” folder:

$ git clone git://github.com/ialexi/pomona.git frameworks/pomona

Next, just like you add the project in Django, you need to add the framework to your SproutCore project. To accomplish this, you edit the “config” line of “Buildfile” to look like this:

config :all, :required => [:sproutcore, :pomona]

You changed it from the solitary :sproutcore, to the array [:sproutcore, :pomona].

Next, you need to proxy Roots’ long-polling server. Just add this line at the end of Buildfile after the other two proxy lines:

proxy "/comet/", :to => "localhost:8008", :url=>"/"

Subscribing to Events

There are two things that have to happen to subscribe to events: create and configure an instance of the long-poller, and call connect().

The easiest place to do this is in an init function in our data source. Just place this code in the DataSource definition (preferably at the top).

  init: function(){
    // DataSource has no init right now, but what if it adds one?
    // so, call super function first.
    sc_super(); 
 
    // needs proxy /comet/ to dobby
    this.firenze = Pomona.Firenze.create({    // Firenze is the name of the long-poll system.
      connectUrl: "/tasks/connect/%@",        // %@ is replaced with the client id
      disconnectUrl: "/tasks/disconnect/%@"
    });
 
    // tell it to call this.taskReceived when a message is received on path "tasks"
    this.firenze.connect("tasks", this, "taskReceived");
  },

The connectUrl/disconnectUrl parameters tell Pomona what URLs to send connect and disconnect requests to. All it does is send a JSON array of path (event) names in a JSON array). It will automatically re-send these messages if the connection with the long-polling server is interrupted and a new connection (with a new id) is created.

All that’s left now is to receive messages!

Receiving Messages

Receiving messages is simple: we just implement the “taskReceived” function we told it about earlier.

  taskReceived: function(path, message) {
    if (message.trim() === "") return;  // the first time, we receive "" to confirm connection
    var data = JSON.parse(message);   // we sent a JSON-encoded message from Python
 
    if (data.DELETE) {
      // We set DELETE if the record needed deleting
      Todos.store.pushDestroy(Todos.Task, data.guid);
    } else {
      // otherwise, we should just re-load that record.
      Todos.store.loadRecords(Todos.Task, [data]);
    } 
  },

That’s All!

Start sc-server, open two browsers, both pointing at http://localhost:4020/todos, and hopefully, you’ll have Comet.

There may be some bumps—if anyone is willing to test and offer feedback, please do so!

Good luck!

3 Responses to “SproutCore, Todos, Django, and Comet in < 100 Lines of Code”

  1. [...] Open Question: Would anyone like a tutorial on setting up a Django back-end for the SproutCore Todos tutorial, with an extra step of adding Comet at the end? Done! [...]

  2. Ben says:

    Well, just when you thought you knew what you could do with django….POW, a blog comes up and kicks your ass. Nice work.

Leave a Reply