Creating modern web applications with Django and ember.js JavaScript framework
emberjs is a new JavaScript framework for building interactive/responsive web applications. Compared to "classical" approach it removes the need to write code for AJAX requests fetching, modifying data or for example code responsible for DOM manipulation. Like in good framework we write only the code implementing our app functionality/idea.
In the same category we can also find other similar frameworks like Backboje.js or Angular.js. Each has its strong and weak points.
In this article I'll showcase basic ember features and ember application development process for a Django-Tastypie-Ember application. You need at least basic Django and django-tastypie knowledge (REST API-making application for your models). The whole application developed in this article can also be found on github.
Installation and configuration
On the Python side we will need Django 1.5 or newer (or older with backported "verbatime" template tag). We start with a fresh Django project and application:
django-admin.py startproject ember_showcaser
cd ember_showcaser/
django-admin.py startapp quotes
mkdir quotes/static
mkdir quotes/templates
'tastypie', 'south',
Add also the quotes application created in previous step.
And configure database settings as you desire. I used a SQLite3 database:manage.py syncdb manage.py migrate
ember.js installation
Ember.js framework as a whole consists of few JavaScript files that we have to download and build if needed (or use those provided in this article).
From emberjs.com download ember.js and ember-data.js. From handlebarsjs.com download handlebars.js. From a github ember-data-tastypie-adapter repository fetch (packages/ember-data-tastypie-adapter/lib) tastypie_adapter.js and tastypie_serializer.js.
When we have all the JS files we can start making our first Ember/Django application. The initial step is to make a plain template view showing a basic template with all those JavaScript files included.
Basic view
In quotes/views.py create a typical TemplateView:from django.views import generic
class MainPageView(generic.TemplateView):
template_name = 'index.html'
main_page = MainPageView.as_view()
url(r'^$', 'quotes.views.main_page'),
<!doctype html>
<html>
<head>
<title>Ember.js showcase</title>
<meta charset="utf-8"/>
<script src="{{ STATIC_URL }}js/vendor/jquery-1.11.0.min.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}js/vendor/handlebars.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}js/vendor/ember.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}js/vendor/ember-data.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}js/vendor/tastypie_serializer.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}js/vendor/tastypie_adapter.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}js/quotes.js" type="text/javascript"></script>
</head>
<body>
<h1>Ember.js Showcase Quotes App</h1>
</body>
</html>
At this point we should have a working view that has all the JS files and no errors in the browser JavaScript console. Note that file order must be kept as shown to satisfy dependencies.
Ember.js Application
Structure of an ember application
As you can read on ember website the framework has few layers, similar to Django or other frameworks. I won't repeat what's written there. I'll just give a quick overview from a Django developer point of view.
Templates are managed by the handlebars library, which uses quite similar tags to those from django templates. The newly added verbatim template tag prevents rendering of Django template tags - thus preserving handlebars templates used by the ember application. As handlebars can't include templates from external files usually all handlebars templates are in one file - one after another (although you could use Django to include verbatimed file-based-templates into the primary template file).
Controller is something like a view in Django. This object stores the state of the application (a part of it). Compared to Django view this object is "alive" as it can respond to events or cause template re-rendering etc. There is a lot of event-driven (asynchronous) behavior in ember application in general.
Views are a layer between a controller and a template. They are used to translate primitive events like click, submit into meaningful evens handled later by the controller (like "createQuote" and not "submit"). In simple cases we won't even have to define a view for given template-controller set.
Models are quite similar layer to Django models. In Django-Ember applications they will reflect your Django models quite close. As ember-data can't do Python Tastypie is used. It creates a REST JSON API which is used by the ember-data tastypie adapter to provide read/write functionality for the ember models. Models in ember are also asynchronous. Executing Model.find() to fetch all entries won't return them but an object that is fetching them and will have them "in a moment". That asynchronous nature affects how controllers or in general ember applications work and are codded - something a Django developer may not be used to.
Routing or route-objects are used to map templates (like urls.py), but also to match (by keeping matching naming) a template with a controller and a model object. In a full blown configuration router tells the controller to represent a data object from the model and render the template for it. In a simple case it's just something like urls.py, while in extended version it can do some extra features like doing stuff on init.
With bigger apps it's good to split the JavaScript code into separate files - for models, controllers, views and one or two for routers (one can have the url map, the second one extended routers). In this article I used one file to make it easier to show the code of a simple application.
Creating an ember application
We will use quotes.js for JavaScript code and index.html for handlebars templates. Starting with the template we define the primary application template:
<body>
{% verbatim %}
<script type="text/x-handlebars">
<h1>Ember.js Showcase Quotes App</h1>
{{outlet}}
</script>
{% endverbatim %}
</body>
Application renders the primary template and then puts the HTML code of the url-specific template in the place of "outlet" tag. In this template we can create stuff like header, footer, some navigation. Without verbatim tag the outlet would have been render by Django.
In JavaScript we have a short create call:
(function($, undefined ) {
Quotes = Ember.Application.create();
}(jQuery));
Django models and Tastypie resources
tastypie is a handy app that will make an REST API for use to use in ember application. To make a Tastypie resource we first need to have a Django model, like this one:
from django.db import models
class Quote(models.Model):
quote = models.CharField(max_length=300, unique=True)
poster = models.ForeignKey('auth.user')
posted_date = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.quote
from django.contrib.auth.models import User
from tastypie.resources import fields
from tastypie.resources import ModelResource
from quotes import models
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
allowed_methods = ['get']
always_return_data = True
fields = ['username']
class QuoteResource(ModelResource):
poster_id = fields.ForeignKey(UserResource, 'poster')
class Meta:
queryset = models.Quote.objects.all()
allowed_methods = ['get']
always_return_data = True
from django.conf.urls import patterns, include, url
from django.contrib import admin
from tastypie.api import Api
from quotes import resources
admin.autodiscover()
v1_api = Api(api_name='v1')
v1_api.register(resources.UserResource())
v1_api.register(resources.QuoteResource())
urlpatterns = patterns(
'',
url(r'^$', 'quotes.views.main_page'),
url(r'^admin/', include(admin.site.urls)),
(r'^api/', include(v1_api.urls)),
)
Next step is to make Ember models that will use those resources to get/put data.
Creating Ember.js models
When we have working Tastypie resources we can start creating models in ember application. Data aspects in ember are handled by ember-data. Here we have models for our resources:
(function($, undefined ) {
Quotes = Ember.Application.create();
Quotes.ApplicationAdapter = DS.DjangoTastypieAdapter.extend({});
Quotes.ApplicationSerializer = DS.DjangoTastypieSerializer.extend({});
var attr = DS.attr;
Quotes.Quote = DS.Model.extend({
quote: attr('string'),
poster: DS.belongsTo('user'),
posted_date: attr('date')
});
Quotes.User = DS.Model.extend({
username: attr('string')
});
}(jQuery));
At start we define a data adapter and serializer which will be used by ember-data. Model names are matched to the API resources names. For longer names made of more than one word (like a FooBarResource) we may need to add resource_name that matches what the adapter wants (you will see requests in the browser console).
This is the third place where we have to describe our models. Each field has a type like string, number, boolean, or date. Relations are done via DS.belongsTo, or DS.hasMany (which needs a reverse DS.belongsTo relation defined too).
We now have working models. this.store.find('quote') will return a promise that at some point in time it will have all quotes. It's different from synchronous Django applications making. We will use that now to show a list of quotes on the main page.
Routing and templates
Lets start by adding a new template:{% verbatim %}
<script type="text/x-handlebars">
<h1>Ember.js Showcase Quotes App</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="quotes-list">
{{#each quote in content}}
<article>
{{quote.quote}}
<p>Added by: {{ quote.poster.username }}</p>
</article>
{{/each}}
</script>
{% endverbatim %}
PSo we made a "quotes-list" template. In that template we iterate over "content" - a list of quotes. Based on the template name the URL is generated - "/#/quotes-list" (if we don't change that behavior).
Now we have somehow set a list of quotes under that variable. Also we have to add this template to the templates map. Routing is the place for that:
Quotes.Router.map(function() {
this.route('quotes-list');
});
Quotes.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('quotes-list');
}
});
Quotes.QuotesListRoute = Ember.Route.extend({
model: function() {
return this.store.find('quote');
}
});
Router.map is a "simple" map of all used templates - in our case one template. Next in line are two extended routes - primary Quotes.IndexRoute and Quotes.QuotesListRoute for the template (note the naming reflecting template name). When we enter the root URL (/) we will be redirected to the quotes-list template URL. When the routing for that template is launched it will assign the list of quotes (object that will have it when it fetches it) to the variable content used in the template.
If you look closely you will notice that the username shows after the quotes texts are shown. This is caused by the relation. When we want to display the username in the template ember makes a query to the API for the user data with username. If we had a tree of related objects those would load one after another and template would notice changes (object fetched) and render data from them as they are fetched.
Template controller (built in controller for now) ability to watch for changes and take actions/re-render part of the template without reloading the page and without the need for use to code all the annoying stuff is one of the key features of this framework.

List of quotes displayed by ember
At first many things in ember may look like magic (events and asynchronous actions), but when you look how this basic app is being made and how it works - the magic will become clear (or you will become a sorcerer which is cool anyway :)). If you have questions google for solutions or ask directly on stackoverlow, check the documentation or visit #emberjs IRC channel on Freenode.
Navigation
Lets play with something simple, yet cool - navigation and making links to templates with "linkTo" tag. To do that I'll created two simple templates: <script type="text/x-handlebars" data-template-name="about">
<h2>About the application</h2>
<p>This application rocks!</p>
</script>
<script type="text/x-handlebars" data-template-name="contact">
<h2>Contact</h2>
<p>Call Houston in case of troubles.</p>
</script>
Quotes.Router.map(function() {
this.route('quotes-list');
this.route('about');
this.route('contact');
});
<script type="text/x-handlebars">
<h1>Ember.js Showcase Quotes App</h1>
<nav>
{{#link-to "quotes-list"}}Quotes{{/link-to}} |
{{#link-to "about"}}About{{/link-to}} |
{{#link-to "contact"}}Contact{{/link-to}}
</nav>
{{outlet}}
</script>

Navigation in action
Creating data records
Now something harder - lets make a template for adding new quotes. This can be done in pure ember-way or the worse, old way with jQuery and DOM manipulation. Knowing the ember way may not be always obvious (that need some experience and more examples in the docs). In this template we will have a form with an input, so I'll use a TextField view embedded in ember. The advantage of using this view is that it binds the input value with a variable available in the controller (in both ways), which makes it easy to fetch/modify the value from the controller (and in other cases to for example watch for changes). Here is the template with the view used:
<script type="text/x-handlebars" data-template-name="add-quote">
<h2>Add a quote</h2>
<form>
{{view Ember.TextField valueBinding="quote" placeholder="Add a quote"}}
</form>
</script>
Quotes.AddQuoteController = Em.Controller.extend({
quote: '',
actions: {
saveQuote: function(text) {
if (text) {
this.store.createRecord('quote', {'quote': text}).save();
this.set('quote', '');
this.transitionToRoute('quotes-list');
}
}
}
});
Quotes.AddQuoteView = Ember.View.extend({
submit: function() {
var text = this.get('controller.quote');
this.get('controller').send('saveQuote', text);
}
});
Let us start with AddQuoteView view. As you can see it "translates" a submit event coming from the template into a "saveQuote" event passed to the controller. The controller creates a new Quote object, commits changes to the backend and then it clears the input value (two way binding). this.set/this.get is the ember way to set/get things. At the end we use "transitionToRoute" to redirect the user to the quotes list where he will see his quote (while save request is probably still not finished). Yet another ember-in-action nice feature.
The save operation will fail in the Tastypie resource as we didn't allowed POST requests for object creation. We have to add it to "allowed_methods" as well add some input validation:
from django.contrib.auth.models import User
from tastypie.authorization import Authorization
from tastypie.http import HttpUnauthorized
from tastypie.resources import fields
from tastypie.resources import ImmediateHttpResponse
from tastypie.resources import ModelResource
from quotes import models
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
allowed_methods = ['get']
always_return_data = True
fields = ['username']
class QuoteResource(ModelResource):
poster = fields.ForeignKey(UserResource, 'poster')
class Meta:
queryset = models.Quote.objects.all()
allowed_methods = ['get', 'post']
always_return_data = True
authorization = Authorization()
def obj_create(self, bundle, **kwargs):
if bundle.request.user.is_authenticated():
return super(QuoteResource, self).obj_create(bundle,
poster=bundle.request.user,
**kwargs)
raise ImmediateHttpResponse(HttpUnauthorized('Not authenticated'))
obj_create method is called when the record is beign created. We override it to set the correct user as poster or return API error if he isn't authenticated (this isn't an exception so logging for production apps is a very good thing to add).
In upcoming articles we will add more features and Tastypie resources will grow in size. More methods will get overridden and more validation will be applied. Note that this API may not return data that current user shouldn't see/get. That's why we override Tastpie methods to for example filter data only to that the current user should see. Security for a public database query tool like this API is an important issue.
The end
This is the end of this article (although there will be more). I've tried to show a quick start with ember, the initial steps to make an application and some big picture on how ember applications work and differ from pure Django apps. You can also get the code from github. I'll add new features in upcoming tutorials.
Comment article