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:

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.
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:
|
$ mkdir todo-djember $ cd todo-djember |
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:
|
$ cd todo-ember $ ember install ember-simple-auth $ ember install ember-cli-scaffold |
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:
|
$ virtualenv -p /usr/bin/python2.7 venv $ source venv/bin/activate $ pip install django $ django-admin.py startproject todo_django |
And we will generate an app inside our project to hold our todo list implementation:
|
$ cd todo_django $ python manage.py startapp todo |
We’re going to need some Django packages to build an API that plays nice with Ember. Install them using pip
:
|
$ pip install djangorestframework $ pip install django-cors-headers $ pip install djangorestframework-jsonapi==2.0.0-beta.2 |
Now add everything to INSTALLED_APPS
in settings.py
.
|
INSTALLED_APPS = [ ... 'django.contrib.staticfiles', 'corsheaders', 'rest_framework', 'rest_framework.authtoken', 'rest_framework_json_api', 'todo' ] |
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
:
|
MIDDLEWARE_CLASSES = [ ... 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', ... ] |
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):
|
CORS_ORIGIN_ALLOW_ALL = True |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', ), 'PAGE_SIZE': 10, 'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler', 'DEFAULT_PAGINATION_CLASS': 'rest_framework_json_api.pagination.PageNumberPagination', 'DEFAULT_PARSER_CLASSES': ( 'rest_framework_json_api.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser' ), 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework_json_api.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ), 'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata', } |
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:
|
from __future__ import unicode_literals from django.db import models class TodoItem(models.Model): label = models.CharField(max_length=512) text = models.TextField(null=True) done = models.BooleanField(default=False) class JSONAPIMeta: resource_name = "todos" |
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:
|
from rest_framework import serializers from models import TodoItem class TodoItemSerializer(serializers.ModelSerializer): class Meta: model = TodoItem fields = ('label', 'text', 'done') |
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:
|
from rest_framework import viewsets from models import TodoItem from serializers import TodoItemSerializer class TodoItemViewSet(viewsets.ModelViewSet): """ API endpoint that allows TodoItems to be CRUDed. """ queryset = TodoItem.objects.all() serializer_class = TodoItemSerializer |
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:
|
from django.conf.urls import url, include from django.contrib import admin from rest_framework import routers from todo.views import TodoItemViewSet router = routers.DefaultRouter(trailing_slash=False) router.register("todos", TodoItemViewSet) urlpatterns = [ url(r'^api/', include(router.urls)), ... ] |
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:
|
$ python manage.py makemigrations $ python manage.py 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:
|
$ ember g scaffold todo label:string text:string done:boolean |
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:
|
{{input type="text" value=model.done}} |
Just change it to this:
|
{{input type="checkbox" checked=model.done}} |
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:
|
$ npm uninstall ember-cli-mirage --save-dev |
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:
|
$ ember g adapter application |
Open up adapters/application.js
and add the following:
|
import DS from 'ember-data'; import ENV from 'todo-ember/config/environment'; export default DS.JSONAPIAdapter.extend({ host: ENV.host, namespace: 'api' }); |
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:
|
module.exports = function(environment) { var ENV = { host: 'https://somehost.com', ... } }; if (environment === 'development') { ENV.host = 'http://localhost:8000'; } ... } |
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
:
|
from rest_framework.authtoken.views import obtain_auth_token ... urlpatterns = [ url(r'^api-auth-token/', obtain_auth_token), ... ] |
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:
|
$ curl -H "Content-Type: application/json" -X POST -d '{"username":"badusername","password":"badpassword"}' http://localhost:8000/api-auth-token/ [{"status":"400","source":{"pointer":"/data/attributes/non_field_errors"},"detail":"Unable to log in with provided credentials."}] |
Now with good credentials:
|
$ curl -H "Content-Type: application/json" -X POST -d '{"username":"goodusername","password":"goodpassword"}' http://localhost:8000/api-auth-token/ {"token":"e5d92c005934e4034b8335e03ee836fae4ceecfd"} |
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:
|
ENV['ember-simple-auth'] = { authenticationRoute: 'login', routeAfterAuthentication: 'todos', routeIfAlreadyAuthenticated: 'todos' }; |
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):
|
$ ember g route application $ ember g controller application |
And add the following code to it:
|
import Ember from 'ember'; export default Ember.Controller.extend({ session: Ember.inject.service('session'), actions: { invalidateSession() { this.get('session').invalidate(); } } }); |
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}} <a {{action 'invalidateSession'}}>Logout</a> {{else}} {{#link-to 'login'}}Login{{/link-to}} {{/if}} |
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:
|
$ ember g controller login |
Open the template for the route – login.hbs
– and add a simple login form:
|
<form {{action 'authenticate' on='submit'}}> <p> <label for="username">Login</label> {{input id='username' placeholder='Enter Login' value=username}} </p> <p> <label for="password">Password</label> {{input id='password' placeholder='Enter Password' type='password' value=password}} </p> <p> <button type="submit">Login</button> </p> {{#if error}} <p>{{error}}</p> {{/if}} </form> |
This won’t work until we implement the authenticate
action on the controller, so open up controllers/login.js
and add the following:
|
import Ember from 'ember'; export default Ember.Controller.extend({ session: Ember.inject.service('session'), actions: { authenticate() { let { username, password } = this.getProperties('username', 'password'); this.get('session').authenticate('authenticator:drf-token-authenticator', username, password).catch((reason) => { this.set('error', reason); }); } } }); |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
import Ember from 'ember'; import Base from 'ember-simple-auth/authenticators/base'; import ENV from 'todo-ember/config/environment'; export default Base.extend({ restore(data) { return new Ember.RSVP.Promise((resolve, reject) => { if (!Ember.isEmpty(data.token)) { resolve(data); } else { reject(); } }); }, authenticate(username, password) { return new Ember.RSVP.Promise((resolve, reject) => { Ember.$.ajax({ url: ENV.host + '/api-auth-token/', type: 'POST', data: JSON.stringify({ username: username, password: password }), contentType: 'application/json;charset=utf-8', dataType: 'json' }).then((response) => { Ember.run(function() { resolve({ token: response.token }); }); }, (xhr, status, error) => { var response = xhr.responseText; Ember.run(function() { reject(response); }); }); }); }, }); |
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:
|
... from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated class TodoItemViewSet(viewsets.ModelViewSet): """ API endpoint that allows TodoItems to be CRUDed. """ queryset = TodoItem.objects.all() serializer_class = TodoItemSerializer authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) |
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:
|
import Ember from 'ember'; import Base from 'ember-simple-auth/authorizers/base'; export default Base.extend({ session: Ember.inject.service('session'), authorize: function(sessionData, block) { if (this.get('session.isAuthenticated') && !Ember.isEmpty(sessionData.token)) { block('Authorization', 'Token ' + sessionData.token); } } }); |
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:
|
import DS from 'ember-data'; import ENV from 'todo-ember/config/environment'; import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin'; export default DS.JSONAPIAdapter.extend(DataAdapterMixin, { host: ENV.host, namespace: 'api', authorizer: 'authorizer:drf-token-authorizer' }); |
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:
|
import Ember from 'ember'; import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin'; export default Ember.Route.extend(ApplicationRouteMixin, { }); |
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:
|
import Ember from 'ember'; import SaveModelMixin from 'todo-ember/mixins/todos/save-model-mixin'; import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; export default Ember.Route.extend(SaveModelMixin, AuthenticatedRouteMixin, { }); |
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.
|
import Ember from 'ember'; import UnauthenticatedRouteMixin from 'ember-simple-auth/mixins/unauthenticated-route-mixin'; export default Ember.Route.extend(UnauthenticatedRouteMixin, { }); |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
@require_http_methods(["POST"]) @csrf_exempt def register(request): """ API endpoint to register a new user. """ try: payload = json.loads(request.body) except ValueError: return JsonResponse({"error": "Unable to parse request body"}, status=400) form = RegistrationForm(payload) if form.is_valid(): user = User.objects.create_user(form.cleaned_data["username"], form.cleaned_data["email"], form.cleaned_data["password"]) user.save() return JsonResponse({"success": "User registered."}, status=201) return HttpResponse(form.errors.as_json(), status=400, content_type="application/json") |
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
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
class RegistrationForm(forms.Form): """ Register a new user. """ username = forms.CharField(max_length=30, min_length=4, label="Username") email = forms.EmailField() password = forms.CharField(widget=forms.PasswordInput(), min_length=5, label="Password") confirm_password = forms.CharField(widget=forms.PasswordInput(), min_length=5, label="Confirm Password") def clean_username(self): username = self.cleaned_data["username"] try: user = User.objects.get(username=username) except User.DoesNotExist: return username raise forms.ValidationError( "The username %s is already taken." % username) def clean(self): """ Make sure that the two passwords match. """ password = self.cleaned_data.get("password", None) confirm_password = self.cleaned_data.get("confirm_password", None) if password == confirm_password: return self.cleaned_data raise forms.ValidationError("The passwords do not match.") |
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:
|
... url(r'^api-register/', register), ... |
Now that the Django side is ready, let’s take care of the Ember side. We have to generate a register
route and controller.
|
$ ember g route register $ ember g controller register |
Then we can put a registration form in register.hbs
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
{{#if signupComplete}} <p>Signup complete.</p> {{else}} <form {{action 'register' on='submit'}}> <p> <label for="username">Login</label> {{input id='username' placeholder='Enter Login' value=username}} </p> <p> <label for="email">Email</label> {{input id='email' placeholder='Enter Email' value=email}} </p> <p> <label for="password">Password</label> {{input id='password' placeholder='Enter Password' type='password' value=password}} </p> <p> <label for="confirm_password">Confirm Password</label> {{input id='confirm_password' placeholder='Confirm Password' type='password' value=confirm_password}} </p> <p> <button type="submit">Sign Up</button> </p> {{#if error}} <p>{{error}}</p> {{/if}} </form> {{/if}} |
Modify the autogenerated register
route to include the UnauthenticatedRouteMixin
:
|
import Ember from 'ember'; import UnauthenticatedRouteMixin from 'ember-simple-auth/mixins/unauthenticated-route-mixin'; export default Ember.Route.extend(UnauthenticatedRouteMixin, { }); |
Now we will implement the register
action on the register
controller that the registration form uses. It looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
import Ember from 'ember'; import ENV from 'todo-ember/config/environment'; export default Ember.Controller.extend({ actions: { register() { let {username, email, password, confirm_password} = this.getProperties( 'username', 'email', 'password', 'confirm_password' ); Ember.$.ajax({ url: ENV.host + '/api-register/', type: 'POST', data: JSON.stringify({ username: username, email: email, password: password, confirm_password: confirm_password }), contentType: 'application/json;charset=utf-8', dataType: 'json' }).then((response) => { this.set('signupComplete', true); }, (xhr, status, error) => { this.set('error', xhr.responseText); }); } } }); |
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:
|
{{#link-to 'login'}}Login{{/link-to}} {{#link-to 'register'}}Register{{/link-to}} |
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:
|
class TodoItem(models.Model): """ An item to be done. """ user = models.ForeignKey(User) label = models.CharField(max_length=512) text = models.TextField(null=True) done = models.BooleanField(default=False) class JSONAPIMeta: resource_name = "todos" |
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:
|
$ python manage.py makemigrations You are trying to add a non-nullable field 'user' to todoitem without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows) 2) Quit, and let me add a default in models.py Select an option: 1 Please enter the default value now, as valid Python The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now() >>> 1 |
Then apply the generated migration:
|
$ python manage.py migrate |
To restrict the todo items to the current user, override the get_queryset
method on the ViewSet
:
|
def get_queryset(self): return TodoItem.objects.filter(user=self.request.user) |
And remove the queryset
:
|
queryset = TodoItem.objects.all() # DELETE THIS LINE |
At this point we need to add the base_name
to the router to avoid errors:
|
... router.register("todos", TodoItemViewSet, base_name="todo") ... |
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
:
|
def perform_create(self, serializer): serializer.save(user=self.request.user) |
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:
|
from rest_framework import permissions class BelongsToUser(permissions.BasePermission): def has_object_permission(self, request, view, obj): return request.user == obj.user |
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:
|
permission_classes = (IsAuthenticated, BelongsToUser,) |
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.