Making Ember and Django play nicely together: a to-do app walkthrough

I’ve wanted to get started building applications with Ember for a while, but I never invested the time to figure out how to integrate it with Django. I’ve spent the last few days doing just that, however, and it’s been a nightmare of outdated libraries and vague documentation. The main obstacle was getting authentication working. At one point I ended up on page 7 of Google, so that will give you an idea of how bad it was. I’m writing this post so you don’t have to go through the same pain.

Here is what we are going to build:

  • An Ember todo list CRUD app
  • Using a JSON API-compliant backend built with Django Rest Framework
  • Secured using token authentication
  • With the ability to login and register new users

Here are some screenshots of the (unstyled) finished application:

new_todo todo_list_index user_login user_registration

This tutorial is for Ember 2.4 and Django 1.9. I recommend that you go through the basic Ember tutorial first to orientate yourself, although I will try to explain everything as I go along.

This tutorial is pretty long. Want a PDF?

Just type in your email address and I'll send a PDF version to your inbox.

Powered by ConvertKit

Setting up the project

We’re going to put the Ember project and the Django project side by side in the same directory. We are not going to embed the Ember project within Django’s static files, as some people do. There is really no reason to do that because the whole point of front-end Javascript frameworks is that they are backend-agnostic – they should work equally well with any backend that implements the necessary API calls. It is not even obvious that we would put the Ember client and the Django API on the same server, so it is best to keep them separate.

Create a directory to hold everything and cd into it:

Use the ember new command to generate an Ember application called todo-ember in that directory:

cd into the directory and install some Ember libraries that we are going to need:

Now we will generate the Django project. We will also start a virtualenv for it. From the root directory – the one where we ran ember new – run the following commands:

And we will generate an app inside our project to hold our todo list implementation:

We’re going to need some Django packages to build an API that plays nice with Ember. Install them using pip:

Now add everything to INSTALLED_APPS in settings.py.

We need the django-cors-headers package to add Cross Origin Resource Sharing headers to our API responses. This will allow our Ember development server running on localhost:4200 to talk to the Django development server on localhost:8000. Add the middleware from that package to MIDDLEWARE_CLASSES :

And set the CORS_ORIGIN_ALLOW_ALL setting to True (this will be fine for development, but don’t run it like that in production):

Now add the Django Rest Framework settings to settings.py. Most of this is overriding Django Rest Framework defaults with classes from the djangorestframework-jsonapi , which make Django Rest Framework conform to the JSON API specification that Ember Data expects. We also set TokenAuthentication as a default authentication class:

Implementing the Django TodoItem model

With the basic project skeleton in place, we can create a model for todo items. Open models.py in the todo app and implement the following:

Creating a serializer for TodoItem

Because we are exposing the model with Django Rest Framework, it is going to need a serializer. Make a file called serializers.py in the todo app directory. It should look like this:

Exposing TodoItem as an API

We can take advantage of standard Django Rest Framework functionality to expose REST endpoints for the model. In views.py in the todo app, add the following:

Before we can actually call the endpoints, we need to register the ViewSet in urls.py.

In todo_django/urls.py, register it as follows:

One important point: when instantiating the DefaultRouter, make sure to pass the trailing_slashes=False argument. Otherwise Django will try to redirect calls to /api/todos to /api/todos/, which confuses Ember Data.

As usual when we create a new Django model, we need to migrate:

Now run the Django development server and go to http://localhost:8000/api/todos. You should see the standard Django Rest Framework browseable API screen.

Generating an Ember scaffold with ember-cli-scaffold

We can use the pretty cool ember-cli-scaffold to automatically generate most of what we need to CRUD todo items on the Ember side. From inside the todo-ember directory, run this command:

You should get a printout of everything that was generated. As you can see, it generated a model, routes and templates for us. You can look through the generated files to see what is going on in them.

Something that sucks about the scaffold is that it generated input text components for the boolean attribute on the model. We can change that easily enough.

Open the handlebars template in todos/-form.hbs and find the form component linked to the model’s done component. It should look like this:

Just change it to this:

It also automatically set up a Mirage mock api server to test against, but we are going to use the Django development server, so we don’t need that in our project. Get rid of it like so:

And it generated an adapter in adapters/todos.js that we won’t need, so delete that too.

Adding an application adapter

Ember Data uses adapters to take requests for information from the store and translate them into calls to the persistence layer. We will create an application adapter that Ember Data will use for all API calls by default:

Open up adapters/application.js and add the following:

You’ll notice that we have imported ENV from todo-ember/config/environment.js. This is the Ember equivalent of Django’s settings.py where we can set up different options for when the app is running in dev, test or production.

Let’s set values for ENV.host in there. We’re pointing to the Django development server in development mode:

With that in place, the Ember development server will direct all its API calls to localhost:8000 where your Django development server is running.

Start both servers now and in your browser go to http://localhost:4200/todos. You should find that you can create, read, update and delete records on the server from your Ember frontend. Keep an eye on the Django dev server output to see the requests going through.

Setting up Django for token authentication

So far we’ve built a lot of functionality with nor much code, but we’ve got a problem: anybody can access the server and change our data. Let’s fix that so that only registered users can access the todos route.

We are going to use the token authentication mechanism that comes with Django Rest Framework.

First we will add endpoint in Django that our Ember application can call to receive a token that it will use to validate future API calls. Edit urls.py:

How does this endpoint actually work?

We are going to set up our Ember application to POST some JSON to the endpoint. The JSON will contain a username and password. If the username and password are correct, the endpoint will return a 200 response containing a token.

If the username and password are not correct, it will return a 400 response with an error message. You can try it out with CURL.

First, let’s see what it does with bad credentials:

Now with good credentials:

We will use an Ember addon called Ember Simple Auth to store this token and add it as a header to future API calls.

Setting up Ember Simple Auth

In the Ember project, add the following settings to environment.js. They control the behaviour of Ember Simple Auth:

The code is quite self explanatory, but if you don’t get it right now, relax. You will see what those settings are used for later when we start adding route mixins.

Now generate an application controller (you’ll need to generate a route first):

And add the following code to it:

We are implementing the invalidateSession action on this controller so we can have a logout link on every page. Add the following snippet to the application template in application.hbs:

Creating the login form and controller

Let’s generate an Ember route called login where our login form will live.

While we’re at it, let’s generate a controller for the route:

Open the template for the route – login.hbs – and add a simple login form:

This won’t work until we implement the authenticate action on the controller, so open up controllers/login.js and add the following:

The code in this file deserves some explanation. At the top of the controller, we are injecting the Ember Simple Auth session service, which manages session state for the application.

Then, in the authenticate action, we are calling authenticate on the service. You have probably noticed that the first argument to authenticate is drf-token-authenticator. This refers to a custom Ember Simple Auth authenticator that we have not implemented yet, so let’s do that.

Implementing a custom authenticator

Inside your ember app directory, make a directory called authenticators. Inside that directory, make a file called drf-token-authenticator.js This is where our custom authenticator will live.

Add the following code to the file:

A few words of explanation are required:

We are extending the Ember base authenticator and implementing the restore and authenticate methods.

restore “restores the session from a session data object. This method is invoked by the session either on application startup if session data is restored from the session store.” If we don’t implement this then if we log in and then refresh the page for instance we will be kicked back to the login screen.

authenticate is where we call the /api-auth-token/ endpoint we created in Django. The method returns an Ember Promise that resolves or rejects based on the response to the API call.

The API call itself is made with jQuery, which is embedded into Ember. Notice that we are using the ENV.host that we placed in environment.js earlier to direct the AJAX request to the proper endpoint.

If you run the application now and go to http://localhost:4200/login you should see a login form. Try to log in with an invalid username and password first. You should see the error JSON from the server underneath the form. Then try it with a valid username and password. You should see the “login” link at the top of the page change to a “logout” link.

Securing the API

So far so good, but we’re not actually preventing anyone from sending unauthenticated requests to the API. To do that, we need to edit the ViewSet in Django:

Notice that we added two new properties – authentication_classes and permission_classes.

Right now if you go to http://localhost:4200/todos you will find that our application is broken. Django expects each incoming request on the /todos/ route to have a valid Authorization header with the value Token my_token_value. In order to automatically add that header to outgoing requests, we need to write a custom authorizer.

Writing a custom authorizer

Authorizers are components of Ember Simple Auth that add authorization information to requests made by Ember.

Make a directory inside your app called authorizers and make a file inside that directory called drf-token-authorizer.js. Add the code below to that file:

We are extending the base authorizer from Ember Simple Auth and implementing authorize on it. This checks that the session is authenticated, grabs the token from the passed in sessionData variable, and adds it as a header by calling the passed in block with the header name and the header value.

To make sure that the header is added to all API calls that Ember makes, we need to modify the application adapter. Open adapters/application.js and change it from what we had earlier:

You can see here that we are importing the DataAdapterMixin from Ember Simple Auth and adding it to the adapter as a mixin. We also need to set up authorizer to point to our custom authorizer.

At this point, if you try your application again you should be able to successfully view, edit, create and delete todo list items.

Adding Ember Simple Auth route mixins

A problem with our application right now is that it does not prevent you from accessing the todos route without logging in (although after the last change the API call to fetch the todo items won’t work). We would like users who go straight to todos without logging in to be sent instead to the login route.

It doesn’t automatically send you to todos after login either.

And it would be great if users who go to login while already logged were sent back to todos.

It is quite simple to achieve these things using route mixins. The first one we need to add is in the application route.

Open routes/application.js and change it so it looks like this:

This will enable Ember Simple Auth to automatically change the route based on the authentication state.

Now we need to protect the todos route so that it cannot be accessed without being logged in. Open each route in routes/todos/ and add the AuthenticatedRouteMixin. For example, routes/todos/edit.js should look like this:

Do the same for the nested index and new routes.

If you go to http://localhost:4200/todos now without being logged in you will be kicked back to the login page. Where you go depends on the value for authenticationRoute that we added to environment.js earlier. routeAfterAuthentication and routeIfAlreadyAuthenticated control where you do when you log in and when you come back to the site after already being logged in.

The last mixin we need to add is the UnauthenticatedRouteMixin in the login route. It prevents authenticated users from seeing the login page. Here is what your routes/login.js should look like after you add it.

Registering users

At the moment all we can do is log in with existing users. Nobody else can register an account. Let’s fix that.

We’re going to need a registration endpoint in Django that Ember can use. Here is a one I pulled out of an old project. It’s not going to win any beauty contests, but it does the trick:

It relies on a user registration form that takes a username, email address, a password and a copy of the password to make sure they are the same. Here it is (it lives in forms.py):

This is all pretty standard Django stuff, but note the use of JsonResponse and the use of the HTTP status code to signal the outcome of the request.

The view has to be added to urls.py, naturally:

Now that the Django side is ready, let’s take care of the Ember side. We have to generate a register route and controller.

Then we can put a registration form in register.hbs.

Modify the autogenerated register route to include the UnauthenticatedRouteMixin:

Now we will implement the register action on the register controller that the registration form uses. It looks like this:

This follows the same pattern as the login form. We grab the values in the form fields then use JSON.stringify to assemble them into a JSON payload that we POST to the server. The server then returns either 201 Created response or a 400 response with information about what went wrong.

If the success callback is fired, we hide the login form and show the “Signup Complete” message. Otherwise, we write the error message below the form.

In the same was as with the login form, we are just writing the error responses in raw JSON below the registration form. Exactly how you alert the user to errors will depend on your CSS framework and the facilities it provides for, e.g. form element highlighting, etc. In any case, it is a trivial matter to display the errors more nicely on the page, so we won’t waste time with that today.

Now you should be able to register new users and log in with them.

Make a link to the registration form by editing the application.hbs template. Change the line with the “login” link to include a “register” link too:

Linking Todo items to users

Right now when users log in they see all the todo items and not just the ones that belong to them. Let’s fix that so that todo items are linked to users and users can only see and edit their own.

Add a foreign key to the TodoItem model that points to the user:

Ass with all model changes, we need to migrate. When we run makemigrations we will be prompted to specify a default value for the user field on existing rows. Just give it the primary key of an existing user:

Then apply the generated migration:

To restrict the todo items to the current user, override the get_queryset method on the ViewSet:

And remove the queryset:

At this point we need to add the base_name to the router to avoid errors:

When new todo items are created, we want them to be linked to the current user. We can achieve that by overriding perform_create on the ViewSet:

We also want object-level permissions that prevent people from directly accessing or modifying other users’ todo items, so we will implement a custom Django Rest Framework permission class.

You can put the following code anywhere, but I like to put it in a file called permissions.py in the todo app:

This class implements has_object_permission(self, request, view, obj) from the base class and performs a simple check to see if the user on the object is the same as the authenticated user.

Now change permission_classes on the ViewSet to apply this permission:

That’s it! At this point if you register a bunch of users they will all have their own todo items.

The end

Phew! This has been a pretty long post, but if you have followed along you have gotten over the biggest initial hurdles of working with Ember and Django.

Check out the resources in the next section to learn more.

Resources

The Ember Quickstart Guide should be the first destination in your Ember journey.

The Ember Simple Auth documentation will help you get to grips with this indispensable Ember library.

The Django Rest Framework JSON API package makes linking Ember Data and Django Rest Framework pretty seamless.

Built With Ember showcases the sophisticated user experience that can be achieved with this cool framework.

Download Mastering Decorators

Mastering_decorators_cover

Enjoyed this article? Join the newsletter and get Mastering Decorators - a gentle 22-page introduction to one of the trickiest parts of Python.

Weekly-ish. No spam. Unsubscribe any time. Powered by ConvertKit
  • Raphael Lechner

    Thank you for sharing your tutorial! I found a typo: router.register(“todoitems”, TodoItemViewSet)
    should probably by router.register(“todos”, TodoItemViewSet)

    • Evan Dempsey

      Hi Raphael,
      You are right, thanks. I changed something in the test project and forgot to update the post.
      I’ll update it now.

      • Raphael Lechner

        Very nice tutorial!!!. Thank your guide I have now a running ember+django system 🙂 (there is also a typo in register.hbs, the error message is stored in “error” and not in “errormessage”)

        • Evan Dempsey

          Cheers! I have updated it now. Thanks for letting me know about the typos.

  • hdra

    How are you finding the JsonApi schema for things outside of CRUD operations? For example, I find file upload to be incredible hard to fit into the JsonApi schema.

    Any suggestions on getting around this?

    • Evan Dempsey

      The API is based around resources that you create and modify, so files fit pretty well into that, imo.

      If you want to keep it totally JSON API you could send the file Base64 encoded as part of the JSON body of a PUT request. There is overhead with this method, of course, but depending on your application it might not matter to you.

      Alternatively you could just step outside the specification completely and build a different endpoint where you send the file the normal way using multipart/form-data. It looks like Dropbox does something like this: https://www.dropbox.com/developers-v1/core/docs#files_put

      They put the params in the URL because the whole request body is file data.

  • Pingback: How to throttle a Django view | SmallSureThing()

  • Jonhatan David Fajardo

    Hi.. Thank you for your tutorial.. I have a problem, when go to login or register only show the main route.. have you a repo in GitHub? Thanks 🙂

    • Evan Dempsey

      Not yet. Have you done the stuff in the section titled ADDING EMBER SIMPLE AUTH ROUTE MIXINS?

  • jonwhittlestone

    Hi, thanks for taking the time to post this. I got so far as “Start both servers now and in your browser ”

    Yet the Ember application is trying to go to /api/ on port 4200 despite having the following in todo-ember/config/environment.js

    if (environment === ‘development’) {

    ENV.host = ‘http://localhost:8000/’;

    }

    Any advice greatly welcomed.

    Ps. npm uninstall mirage didn’t work – npm WARN EPEERINVALID [email protected] requires a peer of ember-inflector@^1.9.4 but none was installed.

    I set, ember-cli-mirage.enabled to false

  • David

    Hey! this was a very helpful tutorial! thankyou!

    I am having some trouble when I try to go to localhost:4200/names (wich is in my case my todoItem) I get the following error:

    Error while processing route: names.index “You must include an ‘id’ for name in an object passed to ‘push’ ”

    and it doesn’t even render the Name List, I am new with ember and django but I did every step on this tutorial, I tried to see if there is a problem in my app/routes/names/index.js and when I change this part:

    model: function() {
    return this.store.findAll(‘name’);
    }

    for something static like:

    model: function() {
    return [{
    “_id”: “1”,
    “name”: “SomeName”
    }
    }

    it renders the Name List template with that one value. But from here I don’t really know what to do, I thougt django might be sending the json response on a wrong format but it shows the jsonAPI format on the browsable django API screen so I don’t think that’s the problem. Could you please help me?

  • rodolfojcj

    Hello!

    Thanks for this full example covering both Django and Ember.js sides. There is an alternative to deal with the Ember.js adapter: use the Ember Django Adapter (EDA) available at https://github.com/dustinfarris/ember-django-adapter, which could save these steps:

    – Adding an application adapter on the Ember.js side
    – Depend on the djangorestframework-jsonapi component on the Django side

    Note that the approach taken by EDA is to adapt the Ember.js side to the workings of Django REST Framework (DRF), instead of adapting the Django side to the workings of Ember.js, like you have exemplified in the To-Do App.

    Right now I’m in the process of integrating Ember.js and DRF in a project of mine, using EDA and also taking your example as a guideline. When I finish I expect to confirm or deny if this alternative I’m exposing works in reality.

    Thanks again!

    • rodolfojcj

      I have not yet finished my project and after some real evidence I confirm that using EDA is a viable alternative.

      Nonetheless, an adapter file is still needed to deal with authorization tokens managed by ember-simple-auth. So, an adapter file with a path at adapters/application.js and functionally equivalent to the adapter used in the To-Do sample application of the tutorial, could look like this:

      import DRFAdapter from ‘./drf’;
      import DataAdapterMixin from ’ember-simple-auth/mixins/data-adapter-mixin’;

      export default DRFAdapter.extend(DataAdapterMixin, {
      authorizer: ‘authorizer:drf-token-authorizer’
      });

      • Evan Dempsey

        Thanks. I evaluated EDA before I wrote this, but I ran into some problems so I didn’t go that way. Can’t remember exactly what happened exactly, but I think maybe I missed the adapter.

  • rodolfojcj

    Hi again 🙂

    By any chance, do you have an idea or an example about adding pagination to the List of To-Dos?

    I’m experimenting with that and I would like to have a reference of the involved details.

    Thank you!

  • rodolfojcj

    What is the job of SaveModelMixin? I saw it referenced for the routes/todos/edit.js file without more details.

    Thanks.

  • Ah

    in this part

    We are implementing the invalidateSession action on this controller so we can have a logout link on every page. Add the following snippet to the application template in application.hbs:

    {{#if session.isAuthenticated}}
    Logout
    {{else}}
    {{#link-to ‘login’}}Login{{/link-to}}
    {{/if}}

    you should tell that an “{{outlet}}” must be at the end

    by the way nice tutorial