Backbone.js and Django (unchained)

A short tutorial on how to go ahead and get wrangling with backbone.js and Django.


The purpose of writing this blog post is to share what little I learned while trying to create a simple CRUD app using Django ( a Python based web framework) and Backbone.js in the front end to create a more responsive client side experience.

Click here for a DEMO of the APP (after a lot of styling, for which I used Bootstrap 2.3.3)

Its a long tutorial, so feel free to take breathers in between.

All the used code is available at the github repo . Please post any corrections or suggestions in the comments section and we can try to get it on the groove.

Pre-requisites : A fair understanding of Javascript and MVC framework in general is suggested.

I’ll try to structure this post by first pointing you to an awesome tutorial to understand Backbone and CRUD and MVC in general . In the second part we’ll try to add, server side persistence with using Django and creating an API to access the model.

Still with me? Fine, so we can start warping into the deep space now !

Not so much ? Well me neither, how about we take it one step (or maybe two) at a time.

About Backbone

Backbone.js is a JavaScript framework, among many others, that is gaining special attention in the web development community because it’s ease of use and the structure that it provides to JavaScript applications.

A common problem with large JS web application developed is that they can become pretty messy really quickly. The lack of structure makes the code hard to maintain. This is where Backbone comes into play. It provides structure to organize the code and increase maintainability. Backbone is not the only framework like this. Google this and you’d know. My reason on why we chose backbone is because it has a rich community, great docs and tons of tutorials.

Backbone.js has a hard dependency on underscore.js and soft dependency on jQuery. Its major components are :

  • Views
  • Events
  • Models
  • Collections
  • Routers

Finally, some coding to kill the boredom !

So, instead of teaching you something which many awesome people have already done, I’ll point you to an awesome four course meal ( err, tutorial ) written in small consumable proportions by Adrian Mejia.

Here take this all expenses paid trip to Adrian’s blog on understanding Backbone and CRUD.

Back ?? Awesome, lets head forward.

We are building a simple app where you can add a book, remove it or mark it as read or unread . Yea, we are kind of postponing the super awesome complex app for a later time. The idea is to get up and running and observe certain characteristics ( like the sync queue) of Backbone.js

Hold on sec there, make sure you use the specific version as in the repo. Because, there may be certain dependency conflicts.

The few available solutions, when I created the app were having certain build problems.

Since Backbone assumes a REST architecture, we will be needing some way to make our Django models accessible to it .
For this purpose, I used TastyPy a simple and minimalistic way to write RESTful API for our app.

I’ll link the github repo app again for brevity ! I’ll do a walk-through of the code here.

Our models.py , which is where we defined the default Django model.

from django.db import models
class Book(models.Model):
message = models.CharField(max_length=140)
author = models.CharField(max_length=50, default="DummyAuthor")
flag =models.CharField(max_length=7, default="Unread")

Our resource, the book here, has three characteristics the message (or book name, I used message so that you can relate to the ToDo list example you read before), the author name, and a flag which decides if its been read or not. Validations put in the code are self explanatory.

RESTful deliciousness

Using TastyPy for writing our API , we create a resource.

from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
from books.models import Book
class BookResource(ModelResource):
class Meta:
queryset = Book.objects.all()
#authorization = DjangoAuthorization()
authorization = Authorization()
list_allowed_methods = ['get', 'post', 'put', 'delete']
detail_allowed_methods = ['get','post', 'put', 'delete']

books.models import Book, imports the Book model and makes it available to the API.
queryset : Holds, the aggregate of all the objects under Books model.
list_allowed_methods , detail_allowed_methods states, the operations permitted on this resource.

Then we create the urls.py , here we define the url patterns (regex) on how to access this resource.

from django.conf.urls.defaults import patterns, url, include
from books.api import v1
from .views import IndexView, DetailView
urlpatterns = patterns('',
url(r'^$',
IndexView.as_view(),
name='index'),
url(r'^(?P<pk>\d+)/$',
DetailView.as_view(),
name="detail"),
url(r'^api/', include(v1.urls)),
)

For templating we used, ICanHaz.js (used for client side templating with mustache.js) and defined a template for displaying books, and the basic structure of our page.

Index.html

{% load mustache %}
{% load straight_include %}

<!doctype html>
<html lang="en">
<head>
<title>Django + Backbone.js</title>

<link rel="stylesheet"
href="http://yui.yahooapis.com/3.2.0/build/cssreset/reset-min.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/style.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script src="{{ STATIC_URL }}js/underscore-min.js"></script>
<script src="{{ STATIC_URL }}js/backbone-min.js"></script>
<script src="{{ STATIC_URL }}js/backbone-tastypie.js"></script>
<script src="{{ STATIC_URL }}js/ICanHaz.min.js"></script>


<script>
BOOK_API = "{% url api_dispatch_list api_name="v1" resource_name="book" %}";
{% if data %}
app = {loaded: true};
{% endif %}
</script>
<script src="{{ STATIC_URL }}js/app.js"></script>

<script type="text/html" id="bookTemplate">
{% straight_include "bookTemplate.mustache" %}
</script>

<script type="text/html" id="editBookTemplate">
{% straight_include "editBookTemplate.mustache" %}
</script>

<script type="text/html" id="listApp">
{% straight_include "listApp.mustache" %}
</script>

<script type="text/html" id="detailApp">
{% straight_include "detailApp.mustache" %}
</script>
</head>

<body>
<div id="app" class="container-fluid">
{% if data %}
{% mustache "detailApp" data %}
{% endif %}
</div>
</body>
</html>

Finally, we need to setup our application code in Javascript. First we’ll change Backbone’s sync function, to do a GET upon receiving a HTTP CREATED.

This ensures that on losing connectivity, the PUT queue is cached, and persisted to the backend.
This requires 2 requests to do a create, so you may want to use some other method in production. But then again, the point is to be as verbose as possible.

backbone-tastypy.js

var Backbone = this.Backbone;

/**
* Override Backbone’s sync function, to do a GET upon receiving a HTTP CREATED.
* This requires 2 requests to do a create, so you may want to use some other method in production.
* Modified from http://joshbohde.com/blog/backbonejs-and-django
*/
 Backbone.oldSync = Backbone.sync;
Backbone.sync = function( method, model, options ) {
if ( method === ‘create’ ) {
var dfd = new $.Deferred();

// Set up ‘success’ handling
dfd.done( options.success );
options.success = function( resp, status, xhr ) {
 // If create is successful but doesn’t return a response, fire an extra GET.
// Otherwise, resolve the deferred (which triggers the original //‘success’ callbacks).
 if ( xhr.status === 201 && !resp ) { // 201 CREATED; response null or empty.
var location = xhr.getResponseHeader( ‘Location’ );
return $.ajax( {
url: location,
success: dfd.resolve,
error: dfd.reject
});
}
else {
return dfd.resolveWith( options.context || options, [ resp, status, xhr ] );
}
};

Now that we have taken care of the setting up the sync method, we define our ‘book’ model. Since we have so little logic in this application, we’ll just define the url attribute so we can talk to the server. Tastypie will give the individual books a resource_uri attribute, but if it hasn’t been persisted to the server, it should default to the collections url. We do this because a POST to the collection should add produce a new book.
note: Tastypie also namespaces the resulting JSON, so we’ll need to define a parse function on the collection that will return the objects attribute in the data returned from the server. ( Or we can use one of the many already available options to parse the JSON).

(function(){
window.Book = Backbone.Model.extend({
urlRoot: BOOK_API
});
window.Books = Backbone.Collection.extend({
urlRoot: BOOK_API,
model: Book,
maybeFetch: function(options){

// Helper function to fetch only if this collection has not been fetched before.

if(this._fetched){
// If this has already been fetched, call the success,if it exists
 options.success && options.success();
return;
}
 /* when the original success function completes mark this collection as fetched */
var self = this,
successWrapper = function(success){
return function(){
self._fetched = true;
success && success.apply(this, arguments);
};
};
options.success = successWrapper(options.success);
this.fetch(options);
},
 getOrFetch: function(id, options){
 /* Helper function to use this collection as a cache for models on the server */ 
var model = this.get(id);
 if(model){
options.success && options.success(model);
return;
}
 model = new Book({
resource_uri: id
});
 model.fetch(options);
}
});

Done with this, we go ahead and define our views. The first one is responsible for rendering individual book details ( we call this the detailApp, refer to the source code for more).

 window.BookView = Backbone.View.extend({
tagName: ‘li’,
className: ‘book’,
 events: {
‘click .permalink’: ‘navigate’
},
 initialize: function(){
this.model.bind(‘change’, this.render, this);
},
 navigate: function(e){
this.trigger(‘navigate’, this.model);
e.preventDefault();
},
 render: function(){
$(this.el).html(ich.bookTemplate(this.model.toJSON()));
return this;
}
});

Finally, as we approach the end of the post, the last task and most important one, is responsible for creating the collection of books, loading and displaying them.
It also listens for a click event on the button that says “Book”, and will create a new Book in the collection.
This we call the ‘listApp’.

 window.ListView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, ‘addOne’, ‘addAll’);
 this.collection.bind(‘add’, this.addOne);
this.collection.bind(‘reset’, this.addAll, this);
this.views = [];
},
 addAll: function(){
this.views = [];
this.collection.each(this.addOne);
},
 addOne: function(book){
var view = new BookView({
model: book
});
$(this.el).prepend(view.render().el);
this.views.push(view);
view.bind(‘all’, this.rethrow, this);
},
 rethrow: function(){
this.trigger.apply(this, arguments);
}
 });
 window.ListApp = Backbone.View.extend({
el: “#app”,
 rethrow: function(){
this.trigger.apply(this, arguments);
},
 render: function(){
$(this.el).html(ich.listApp({}));
var list = new ListView({
collection: this.collection,
el: this.$(‘#books’)
});
list.addAll();
list.bind(‘all’, this.rethrow, this);
new InputView({
collection: this.collection,
el: this.$(‘#input’)
});
}
});

A few extra miles : As an extension to this I am trying to use dual sync , that is use local storage adapter(HTML5) for storing data with the browser, even when the browser is closed and re-opened. And persist it to the server, when possible. I am considering Backbone dual sync & Backbone caching sync . If you want to help, please fork the repo and send a pull request.

AND ITS DONE ☺ !!!

We created a very simple app to demonstrate persistence to server with a interactive front end using Backbone.js .

The length and content of the tutorial might have been a little overwhelming to a lot of users. So feel free to touch base with me . Also, for any clarifications, corrections ,comments and criticisms.
I can be reached at :
shivendrasoni91@gmail.com
or find out more about me at About.me

Email me when Shivendra Soni publishes or recommends stories