Building a RESTful Ajax App With Dojo and Python Werkzeug: The back-end
This post is the second post in a series of three that demonstrate how to create a small RESTful Ajax application leveraging the Dojo Toolkit and Python Werkzeug. This post describes the implementation of a RESTful back-end.
We're not the first building a RESTful web service. Tubes is a useful small library on top of Python Werkzeug that helps create such services with a minimal amount of coding effort.
Down to business. Our back-end consist of two files:
- views.py: holding the task views of the application; and
- models.py: holding a task model.
The task model is depicted underneath.
@tubes.JsonClass()
class Task(object):
def __init__(self, name=None, startdate=None, starttime=None, enddate=None, endtime=None):
self.name = name
self.startdate = startdate
self.starttime = starttime
self.enddate = enddate
self.endtime = endtime
Tubes includes a number of decorators that ease development. Our task
model is decorated with the JsonClass
. That class adds
usefull methods that ease JSON-to-Python conversion. A quick example
of how to create and convert a task model to a JSON string.
>>> Task(name="a task").to_json_str()
'{"start": null, "end": null, "name": "a task"}'
To convert the task model into RESTful resources we expose the model via URIs and the methods of HTTP. In tubes again with decorators this is really easy.
import tubes
from models import Task
handler = tubes.Handler()
# For this tutorial we have an in-memory database, which is just a
# common Python dictionary.
TASKS = {}
next_task_id = 0
@handler.post('^/tasks/?$', accepts=tubes.JSON, transform_body=Task.from_json)
def new_task(handler, task):
global next_task_id
task.id = next_task_id
TASKS[task.id] = task
next_task_id = next_task_id + 1
headers = {'Location': handler.url + str(task.id)}
return tubes.Response(task.to_json_str(), status=201, mimetype=tubes.JSON, headers=headers)
@handler.get('^/tasks/?')
def get_tasks(handler):
return Task.to_json_list(TASKS.values())
@handler.get('^/tasks/(.+)/?')
def get_task(handler, id):
if id in TASKS:
return TASKS[id]
return tubes.Response("task not found", 404)
@handler.put('^/tasks/(.+)/?$', accepts=tubes.JSON, transform_body=Task.from_json)
def update_task(handler, task, id):
task.id = id
TASKS[id] = task
return TASKS[id]
@handler.delete('^/tasks/(.+)/?', transform_body=Task.from_json)
def remove_task(handler, id):
if id in TASKS:
del TASKS[id]
else:
return tubes.Response("task not found", 404)
if __name__ == '__main__':
tubes.run(handler)
And that’s it! We have now exposed the tasks model as RESTful
resources. Running views.py
starts a development server;
and we can interact with the tasks resource.
$ python views.py
* Running on http://0.0.0.0:8000/
...
In the following curl is used to issue HTTP requests. Note that uninteresting headers are left out.
List all resources:
curl localhost:8000/tasks/
[]
Create a new resource:
curl -i -H "Content-Type: application/json" -X POST -d '{"name":"a task"}' localhost:8000/tasks/
HTTP/1.0 201 CREATED
Location: http://localhost:8000/tasks/0
Content-Type: application/json
{"startdate": null, "enddate": null, "name": "a task", "starttime": null, "endtime": null, "id": 0}
Edit it:
curl -i -H "Content-Type: application/json" -X PUT -d '{"name":"the task"}' localhost:8000/tasks/0
HTTP/1.0 200 OK
Content-Type: application/json
And delete it:
curl -X DELETE localhost:8000/tasks/0
null
Creating a Dojo front-end
Dojo includes thourough support for interacting with RESTful webservices. The only thing we have do to is point dojo to our exposed resources and we can now do RESTful interaction with that resource through dojo.
dojo.require("dojox.data.JsonRestStore");
tasks = dojox.data.JsonRestStore({target: "/tasks/", idAttribute: "id"});
var t = tasks.newItem({name:"a task"});
tasks.save();
t.__id; // "/tasks/1" <- from the location header in the XHR request.
t.name = "the task";
tasks.changing(t);
tasks.save();
// fetch all tasks and write each task to log.
tasks.fetch({onItem: function(){console.log(arguments)}});
// fetch by identity
tasks.fetchItemByIdentity({identity:"1"});
Dojo includes many different layout widgets. The cool thing with theese widgets is that they support the different dojo datastore. That means we can seamless wire our RESTful resource together with the widgets.