• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Todos 06-Building with Django

Page history last edited by David Chambers 13 years, 7 months ago

Tutorial is in beta. Please read and review. :)

 

Django is a Python-based web framework that makes many tasks, such as managing models, relatively easy.

 

Before you get Started

You need Python.

You need Django, which you can download from the Django Project website. You can find a tutorial specifically on Django (aimed towards web sites rather than web applications) on the Django site as well. If you go through that first (or know Django already), this will probably be a breeze. Otherwise, while you should be able to follow this tutorial, there may be some bumps (if you find some, please give feedback!)

 

Cheating

If you don't have patience, there is a git repository you may clone to get everything pre-set-up. It's cheating, but hey! Also, it has Comet using Roots, so you can even open two browsers at once and edit todos.

 

It is located on GitHub: http://github.com/ialexi/samples-todos

 

You can set it up like this:

$ git clone git://github.com/ialexi/samples-todos.git
$ cd samples-todos
$ git submodule init
$ git submodule update
$ cd server/djangobackend
$ python manage.py syncdb
$ python manage.py runserver
IN A NEW TERMINAL in clone location:
$ sc-server
IN A NEW TERMINAL (to enable Roots comet server)
$ git clone git://github.com/ialexi/dobby.git 
$ cd dobby
$ python dobby.py


 

Creating the Project and Application

Django projects are a lot like SproutCore projects. Each project has one or more applications. Each application defines its own data model. Unlike SproutCore, Django does not seem to differentiate between frameworks and applications.

 

You can put the server-side code anywhere on your filesystem (even, theoretically, on another machine entirely). It may be convenient to place it in a subfolder of your SproutCore project. For instance, you might place it under [SproutCore Project]/server/django.

 

So, let's get started. You'll note that we don't name it django—we don't want any potential namespace conflicts now or in the future with the real django.

 

To create the project, we call django-admin.py:

$ django-admin.py startproject djangobackend

 

The created project has a script named manage.py that will become our very close friend. It does things like run a test server (much like sc-server), generate databases, and (as we are about to do) create applications.

 

So, let us create the app (we have to go into the project directory first, of course):

$ cd djangobackend
$ python manage.py startapp todos

 

Now, if you have an editor that will open the whole project (like TextMate will), it might be a good time to use it. For example:

$ mate .

 

Otherwise, you'll have to find and edit files the old-fashioned way.

 

Setting Up the Model

Creating models is pretty easy in Django. You simply edit the application's models.py file.

 

Here is the model for the Todos application (located under [django_project_path]/todos/models.py ):

 
from django.db import models
# Create your models here:
class Task(models.Model):
     """Our Task Model"""
     description = models.TextField(blank=True)
     is_done = models.BooleanField(default=False)
     order = models.IntegerField(default=0)      
     def __unicode__(self):
          return u"Task"

 

It's pretty simple. Now, you need to make the database.

 

The Database

First, Django needs to know what kind of database you will use. The simplest and quickest to set up is sqlite3—it does not use a server; you just give it a path to a file. While not necessarily recommended, you can usually use a relative path. These settings are in settings.py, located in [django_project_path]/settings.py.

 

Here is an example configuration for the database:

 

DATABASES = {
     'default': {
          'ENGINE': 'sqlite3',
          'NAME' = 'todos.db',
          'USER' = '', # Not used with sqlite3.
          'PASSWORD' = '', # Not used with sqlite3.
          'HOST' = '', # Not used.
          'PORT' = '', # Not used.
     },
}

 

In addition to settings for the database, you have to tell Django that you will be using your app; remember, there can be multiple apps. The app's existence does not indicate its usage. This is in the INSTALLED_APPS setting:

 

INSTALLED_APPS = (
     # 'django.contrib.auth' # Optionally, comment this (makes syncdb a little quicker and smoother)
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.sites',
     'todos', # ADD THIS LINE
)

 

Warning:  Django will not allow you to update an existing table in the database with syncdb. You will need to either start over with a blank database (not necessarily a big deal, but possibly annoying) or add the fields manually.

 

Now, you need to sync the database:

 

$ python manage.py syncdb

 

If there are any errors, it will tell you.

 

Setting Up URLs

Django works by connecting URLs to Views (which are really just special functions). The URL->view mappings are stored in urls.py in the Django project directory.

 

We are going to respond to two different URLs: /tasks and /task/<task-id>

 

from django.conf.urls.defaults import *

urlpatterns = patterns('todos.views',
     # You don't have to do it this way, but in this example, we match
     # optional ending slashes.
     (r'^tasks/?', 'tasks'),
     (r'^task/(?P&lt;taskid&gt;[0-9]+)', 'task'),
)

 

Now, you have to create the views.

 

Views

Views are stored in views.py, a you might have guessed. The following code is relatively easily understandable, and almost all was described in Django's own tutorial.

 

# We will need JSON support
try: import simplejson as json # If simplejson is installed, use that
except ImportError: import json # otherwise, in Python 2.6+, use built-in
          
# Import our model
from models import Task
          
# Response Mechanisms
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect, HttpResponse
          
          
#
# FIRST: some helpers
#
def task_guid(task):
     """ Returns a GUID for a task (in URL format because SC wants that) """
     return "/task/" + str(task.id)
          
def task_to_raw(task):
     """ Converts a task to a dictionary """
     return {
          "guid": task_guid(task),
          "description": task.description,
          "isDone": task.is_done,
          "order": task.order,
    }
          
def raw_to_task(raw, task):
     """ Applies a raw dictionary to a task """
     if "description" in raw: task.description = raw["description"]
     if "isDone" in raw: task.is_done = raw["isDone"]
     if "order" in raw: task.order = raw["order"]
          
# NOW: the views.
def tasks(request):
     # tasks supports GET and POST
     if request.method == "GET":
          # GET should return all of the tasks
          # first, create a list of tasks
          raw_tasks = []
          for task in Task.objects.all():
               raw_tasks.append(task_to_raw(task))
          
          # now, create and return json (remember, front-end wants it in "contents")
          return_data = {
               "content": raw_tasks
          }
          return HttpResponse(json.dumps(return_data), mimetype="application/json")
          
     elif request.method == "POST":
          # Create a Task
          task = Task()
          # Load data
          raw = json.loads(request.raw_post_data)
          raw_to_task(raw, task)
          # attempt to save
          task.save()
          # return just the plain hash
          # returning Location header causes problems
          response = HttpResponse(json.dumps({"content": task_to_raw(task)}))
          response.status_code = 201
          return response
          
def task(request, taskid):
     # In any case, we need that task, so get it or 404:
     task = get_object_or_404(Task, pk=int(taskid))
          
     if request.method == "GET":
          pass # do nothing -- the default action will suffice
     elif request.method == "PUT":
          raw = json.loads(request.raw_post_data)
          raw_to_task(raw, task)
          task.save()
     elif request.method == "DELETE":
          task.delete()
          return HttpResponse("") # return empty response, I suppose
     # Default action: not much
     return HttpResponse(json.dumps({"content": task_to_raw(task)}), mimetype="application/json")

 

Uh oh!

Django does not allow relative URLs in the Location HTTP header and will automatically convert them to absolute URLs. This is incompatible with the todos application. This is done to adhere to the HTTP specification (RFC 2616).

 

To solve this problem, a slightly different way of passing the guid back to the todos application is used. This requires changes to the didCreateTask function to look like this:

 

 
didCreateTask: function(response, store, storeKey) {
     if (SC.ok(response)) {
          var data = response.get("body").content;
          store.dataSourceDidComplete(storeKey, data, data.guid); // update url
     } else store.dataSourceDidError(storeKey, response);
},

 

Starting and Proxying the Server

Now, you need to start the Django server (in a different terminal from sc-server, obviously):

 

$ python manage.py runserver

 

Unfortunately, the SproutCore app can't access the data because it is in a different server. So, you need to have the SproutCore server proxy it. To do this, you edit Buildfile in your SproutCore project directory:

 

 
 
# ===========================================================================
# Project: Todos
# ===========================================================================
 
# Add initial buildfile information here
config :all, :required => :sproutcore
 
# SET UP PROXY
proxy "/task", :to => "localhost:8000"
proxy "/tasks", :to => "localhost:8000"
 

 

All Done

Go to http://localhost:4020/todos and everything should work!

 

Unless there is a mistake—if you find one, please give feedback.

 

Comet

Comet is a technique which enables "push"; for Todos, this means that two people can have the application open (or the app can be open in two windows) simultaneously, and have updates on one be relayed immediately—without page reload—to the other.

 

Adding Comet with Roots — Less than 100 lines of code.

 

Comments (7)

Ryan Williams said

at 12:44 am on Dec 12, 2009

The way syntax highlighting works in this wiki prevents the python code from being copy/pasteable because the indentation is done with CSS margins rather than real spaces/tabs.

JohnRandom said

at 7:57 am on Dec 12, 2009

I corrected a small typo for you Alex:
(r'^task/(?P<taskid>[0-9]+)', "todos.views.task") <-- you had an 's' missing in "todos.view.task"
Also I had problems with cloning your sample-todos. Doing so and running it resulted in a SC Error, because Pomona was undefined. Even after cloning an running Dobby. As I do not yet know anything about Dobby, I did not dove further into that issue. Cloning the official Todos-App and making your modification resulted in a working app. Though I had some issues with intendation as well. But C&P isn't probably the best way to go anyway..
Great tutorial :)

Alex Iskander said

at 9:49 am on Dec 12, 2009

Ryan Williams: The wiki makes all this formatting very difficult. I will see what I can do to fix it—I had it as real spaces/tabs before, but it was causing other issues, so I don't know if I can do anything.

Nearsight:Thanks for fixing that mistake, and for the heads-up on my Git repository. I've fixed it now, I think (problems with Git submodules, which are often a pain). You can follow the instructions again, BUT notice that there are two additional steps (setting up the submodules).

Thanks to both of you for your review. :)

Alex Iskander said

at 9:58 am on Dec 12, 2009

Ryan Williams: I have gone through line by line and replaced with spaces. I'm not sure it will help, but hopefully, it will.

JohnRandom said

at 5:29 am on Dec 13, 2009

Ok, it seems to work nearly perfect now, but I found a bug in your /dobby/dobby/firenze.py. line 61. You have to: 'import simplejson as json' or you run into problems later, when you call 'json.dumps(...'
Aside from that, no problems, tested it in two browsers and the tasks keep popping us soon as add new ones in another browser :)

Alex Iskander said

at 7:28 am on Dec 13, 2009

First: problem solved! Thanks a lot for pointing that out!

And second: thank you very much for taking the time to test this; it helps a lot. :)

David Chambers said

at 7:48 am on Aug 30, 2010

Alex, thanks very much for writing up this tutorial. I updated the database configuration details to use the `DATABASE` dictionary as per the current Django release (1.2). You are right – this wiki's editor is horrible! I saved the change and _all_ the page's code snippets lost their indentation! I've attempted to revert the formatting to how it was before things went crazy; I apologize if I've failed to do so accurately.

One thing I discovered is that Django's CSRF middleware needs to be disabled (it's enabled by default in Django 1.2).

You don't have permission to comment on this page.