神刀安全网

Making Ember and Django play nicely together: a Todo MVC 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:

Making Ember and Django play nicely together: a Todo MVC walkthrough Making Ember and Django play nicely together: a Todo MVC walkthrough Making Ember and Django play nicely together: a Todo MVC walkthrough Making Ember and Django play nicely together: a Todo MVC walkthrough

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:

$ mkdirtodo-djember $ cdtodo-djember 

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

$ embernew todo-ember 

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

$ cdtodo-ember $ emberinstallember-simple-auth $ emberinstallember-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 $ sourcevenv/bin/activate   $ pipinstalldjango $ django-admin.pystartprojecttodo_django 

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

$ cdtodo_django $ pythonmanage.pystartapptodo 

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

$ pipinstalldjangorestframework $ pipinstalldjango-cors-headers $ pipinstalldjangorestframework-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:3200 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 the the JSON API specification that Ember Data expects. We also set TokenAuthentication as a default authentication class:

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.dbimport 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_frameworkimport serializers from modelsimport 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_frameworkimport viewsets from modelsimport TodoItem from serializersimport 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.urlsimport url, include from django.contribimport admin   from rest_frameworkimport routers from todo.viewsimport TodoItemViewSet     router = routers.DefaultRouter(trailing_slash=False) router.register("todoitems", 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 apis/todos/ , which confuses Ember Data.

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

$ pythonmanage.pymakemigrations $ pythonmanage.pymigrate 

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 scaffoldtodolabel: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.js and find the form component linked the 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:

$ npmuninstallember-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 adapterapplication 

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

importDSfrom 'ember-data'; importENVfrom 'todo-ember/config/environment';   exportdefault 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.

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.viewsimport 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 routeapplication $ ember g controllerapplication 

And add the following code to it:

importEmberfrom 'ember';   exportdefault 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.

$ ember g routelogin 

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

$ ember g controllerlogin 

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

<form{{action 'authenticate' on='submit'}}>   <p>     <labelfor="username">Login</label>     {{input id='username' placeholder='Enter Login' value=username}}   </p>   <p>     <labelfor="password">Password</label>     {{input id='password' placeholder='Enter Password' type='password' value=password}}   </p>   <p>     <buttontype="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:

importEmberfrom 'ember';   exportdefault 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 customer 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:

importEmberfrom 'ember'; importBasefrom 'ember-simple-auth/authenticators/base'; importENVfrom 'todo-ember/config/environment';   exportdefault 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:3200/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.authenticationimport TokenAuthentication from rest_framework.permissionsimport 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:3200/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 customer 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:

importEmberfrom 'ember'; importBasefrom 'ember-simple-auth/authorizers/base';   exportdefault 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:

importDSfrom 'ember-data'; importENVfrom 'todo-ember/config/environment'; importDataAdapterMixinfrom 'ember-simple-auth/mixins/data-adapter-mixin';   exportdefault 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:

importEmberfrom 'ember'; importApplicationRouteMixinfrom 'ember-simple-auth/mixins/application-route-mixin';   exportdefault 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:

importEmberfrom 'ember'; importSaveModelMixinfrom 'todo-ember/mixins/todos/save-model-mixin'; importAuthenticatedRouteMixinfrom 'ember-simple-auth/mixins/authenticated-route-mixin';   exportdefault 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.

importEmberfrom 'ember'; importUnauthenticatedRouteMixinfrom 'ember-simple-auth/mixins/unauthenticated-route-mixin';   exportdefault 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:

@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 ):

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 routeregister $ ember g routecontroller 

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

{{#if signupComplete}} <p>Signup complete.</p> {{else}} <form{{action 'register' on='submit'}}>   <p>     <labelfor="username">Login</label>     {{input id='username' placeholder='Enter Login' value=username}}   </p>   <p>     <labelfor="email">Email</label>     {{input id='email' placeholder='Enter Email' value=email}}   </p>   <p>     <labelfor="password">Password</label>     {{input id='password' placeholder='Enter Password' type='password' value=password}}   </p>   <p>     <labelfor="confirm_password">Confirm Password</label>     {{input id='confirm_password' placeholder='Confirm Password' type='password' value=confirm_password}}   </p>   <p>     <buttontype="submit">Sign Up</button>   </p>     {{#if errorMessage}}   <p>{{errorMessage}}</p>   {{/if}} </form> {{/if}} 

Modify the autogenerated register route to include the UnauthenticatedRouteMixin :

importEmberfrom 'ember'; importUnauthenticatedRouteMixinfrom 'ember-simple-auth/mixins/unauthenticated-route-mixin';   exportdefault Ember.Route.extend(UnauthenticatedRouteMixin, { }); 

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

importEmberfrom 'ember'; importENVfrom 'todo-ember/config/environment';   exportdefault 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 the 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:

$ pythonmanage.pymakemigrations Youaretryingto add a non-nullablefield 'user' to todoitemwithout a default; wecan't do that (thedatabaseneedssomethingto populateexistingrows). Pleaseselect a fix:  1) Provide a one-offdefault now (willbesetonallexistingrows)  2) Quit, and letmeadd a default in models.py Selectanoption: 1 Pleaseenterthedefault valuenow, as validPython Thedatetimeand django.utils.timezonemodulesareavailable, soyoucando e.g. timezone.now() >>> 1 

Then apply the generated migration:

$ pythonmanage.pymigrate 

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_frameworkimport 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.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Making Ember and Django play nicely together: a Todo MVC walkthrough

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮