Build a REST API with Django – A Test Driven Approach: Part 2

Jee Gikera
💬 comments

The precondition to freedom is security – Rand Beers

Authentication is a pivotal part of the security of an API.

But first, some recap.

Table of Contents

    In part 1 of this series, we learnt about how to create a bucketlist API using the TDD approach. We covered writing tests in Django and also learnt a lot about the Django Rest Framework.

    We'll be covering complementary topics in part 2 of our series. For the most part, we'll delve deeper into authenticating and authorizing users in the Django-driven bucketlist API. If you haven't checked part 1 yet, now is the chance to do so before we start crushing it.

    Ok... back to business!

    Authentication vs Authorization

    Authentication is usually confused with authorization. They are not the same thing.

    You can think of authentication as a way to verify someone's identify. (username, password, tokens, keys et cetera) and authorization as a method that determines the level of access a verified user should be granted.

    When we look at our bucketlist API, it works for the most part. It however lacks capabilities such as knowing who created a bucketlist, whether a given user is authenticated in the first place or even whether the they have the right to effect changes onto a bucketlist.

    We need to fix that.

    We'll implement authentication first and later drop in some authorization features.

    Implementing it

    Implementing authentication in a DRF API can be done. And the starting point is easy – You start by keeping track of the user.

    So how do we achieve this? Django provides a default User model that we can play around with.

    Ok. Let's get it done.

    We're going to create an owner field on the Bucketlist model. Here's why: A user can create a bucketlist – which means that a bucketlist has an owner. Therefore, we'll simply add a field definition of a user inside our bucketlist model.

    # rest_api/
    from django.db import models
    class Bucketlist(models.Model):
        """This class represents the bucketlist model."""
        name = models.CharField(max_length=255, blank=False, unique=True)
        owner = models.ForeignKey('auth.User',  # ADD THIS FIELD
        date_created = models.DateTimeField(auto_now_add=True)
        date_modified = models.DateTimeField(auto_now=True)
        def __str__(self):
            """Return a human readable representation of the model instance."""
            return "{}".format(

    The owner fieild uses a ForeignKey class that accepts a number of arguments. The first one auth.User simply points to the model class we wish to create a relationship with.

    The foreign key will come from the model class auth.User to enable the relationship between the User and the Bucketlist models.

    After this is done, we'll have to run our migrations to reflect the model changes in our database.

    We'll run

    python3  makemigrations  rest_api

    A point to note: When writing new fields on existing tables, you might encouter this:

    The database complains that we are trying to add a non-nullable field which should not be null or lacking a value. We need a value for it since we have pre-existing data on the database. A simple hack when under a development environment would be to delete the migrations folder inside your app and the db.sqlite3 file. This will get rid of the bucketlist we created last. We can always create a new one. However, you should never do this on a production environment because you'll lose all your DB data. A cleaner way to fix it is to provide a one-off default value. But if you have no records on your db, feel free to go with the deletion fix.

    After doing this, we'll commit the changes to our DB using the migrate command:

    python3 migrate

    Refactoring Our Tests

    So far, we haven't written any tests that work with the new user authentication. We'll therefore have to refactor the existing test cases.

    But first, we've got to know what to write.

    Let's do some analysis. The changes we need to factor in are:

    • Bucketlist ownership by users – which points to integrating the default Django User model
    • Ensure requests made are made by authenticated users – which means we'll enforce authentication before sending HTTP requests
    • Restrict bucketlist(s) creation to only authenticated users
    • Restrict existing bucketlist(s) to be accessed only by their owner

    These points will go a long way in guiding us to refactor our tests.

    Refactoring the ModelTestCase

    We'll import the default User model(django.contrib.auth.User) into our test module to create a user.

    # rest_api/
    from django.contrib.auth.models import User

    The user will help us test for the owner of the bucketlist. We'll create the User in our setUp method so that we don't have to create it every time we want to use it.

    class ModelTestCase(TestCase):
        """This class defines the test suite for the bucketlist model."""
        def setUp(self):
            """Define the test client and other test variables."""
            user = User.objects.create(username="nerd") # ADD THIS LINE = "Write world class code"
            # specify owner of a bucketlist
            self.bucketlist = Bucketlist(, owner=user) # EDIT THIS TOO

    Inside the setup method, we've just defined a test user by creating a user with a username. Then, we've added the instance of the user into the bucketlist class. The user is now the owner of that bucketlist.

    Refactoring the ViewsTestCase

    Since views deals with mainly making requests, we'll ensure only authenticated and authorized users have access to the bucketlist API.

    Let's write some code for it

    # rest_api/
    # import fall here
    # Model Test Case is here
    class ViewTestCase(TestCase):
        """Test suite for the api views."""
        def setUp(self):
            """Define the test client and other test variables."""
            user = User.objects.create(username="nerd")
            # Initialize client and force it to use authentication
            self.client = APIClient()
            # Since user model instance is not serializable, use its Id/PK
            self.bucketlist_data = {'name': 'Go to Ibiza', 'owner':}
            self.response =
        def test_api_can_create_a_bucketlist(self):
            """Test the api has bucket creation capability."""
            self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)
        def test_authorization_is_enforced(self):
            """Test that the api has user authorization."""
            new_client = APIClient()
            res = new_client.get('/bucketlists/', kwargs={'pk': 3}, format="json")
            self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)
        def test_api_can_get_a_bucketlist(self):
            """Test the api can get a given bucketlist."""
            bucketlist = Bucketlist.objects.get(id=1)
            response = self.client.get(
                kwargs={'pk':}, format="json")
            self.assertEqual(response.status_code, status.HTTP_200_OK)
            self.assertContains(response, bucketlist)
        def test_api_can_update_bucketlist(self):
            """Test the api can update a given bucketlist."""
            bucketlist = Bucketlist.objects.get()
            change_bucketlist = {'name': 'Something new'}
            res = self.client.put(
                reverse('details', kwargs={'pk':}),
                change_bucketlist, format='json'
            self.assertEqual(res.status_code, status.HTTP_200_OK)
        def test_api_can_delete_bucketlist(self):
            """Test the api can delete a bucketlist."""
            bucketlist = Bucketlist.objects.get()
            response = self.client.delete(
                reverse('details', kwargs={'pk':}),
            self.assertEquals(response.status_code, status.HTTP_204_NO_CONTENT)

    We initialized the ApiClient and forced it to use authentication. This enforces the API's security. The bucketlist ownership has been factored in as well. Also, notice how we consistently use self.client in each test method instead of creating new ones? This is to ensure that we reuse the authenticated client. Reusability is good practice. :-) Great!

    Run the tests. They should fail for now.

    python3  test  rest_api

    The next step is to refactor our code to make these failing tests pass.

    How To Pass Those Tests!

    First, Integrate the User

    For the most part, any changes you make in your model should be reflected in your serializers too. This is because serializers interface directly with the model which aids in changing weird-looking query sets to json and vice versa.

    Let's edit our bucketlist serializer. We'll simply jump into the file and write a custom field that we'll preferably call owner. This is the owner of a bucketlist.

    # rest_api/
    class BucketlistSerializer(serializers.ModelSerializer):
        """Serializer to map the model instance into json format."""
        owner = serializers.ReadOnlyField(source='owner.username') # ADD THIS LINE
        class Meta:
            """Map this serializer to a model and their fields."""
            model = Bucketlist
            fields = ('id', 'name', 'owner', 'date_created', 'date_modified') # ADD 'owner'
            read_only_fields = ('date_created', 'date_modified')

    The owner field is read-only so that a user using our api cannot alter the owner of a bucketlist. Don't forget to add the owner into the fields as directed above.

    Let's run this and see if it works: Start the server python3 runserver

    When we access it from localhost, we should see something like this:

    Now, we need to make a way to save the owner when a new bucketlist is created. Saving a bucketlist is done in a class called CreateView that we defined in We'll edit our CreateView class by adding a perform_create(self, serializer) method. This method gives us control on how to save our serializer.

    # rest_api/
    # We are inside the CreateView class
        def perform_create(self, serializer):
                """Save the post data when creating a new bucketlist."""
       # Add owner=self.request.user

    The accepts field arguments. Here, we specified the owner argument. Why? Because our serializer has it as a field – which means that we can specify the owner in a serializer's save method that will then save the bucketlist with a user as its owner.

    We should now get an error that looks like this when we try to create a bucketlist The DB complains. Why a Value Error? Good question – It's simply because we are trying to save a bucketlist from the browser without specifying the owner!

    Our new non-nullable owner field needs a value before the serializer can validate and save a bucketlist.

    Let's fix that right away.

    In our, we'll add a route for helping the user to log in to our api before creating a bucketlist. We do this to allow a bucketlist to have an owner, that is if the logged in user decides to create one.

    # rest_api/
    # imports fall here
    urlpatterns = {
        url(r'^auth/', include('rest_framework.urls', # ADD THIS URL
        url(r'^bucketlists/$', CreateView.as_view(), name="create"),
            DetailsView.as_view(), name="details"),
    urlpatterns = format_suffix_patterns(urlpatterns)

    This new line includes the DRF routes that provides a default login template to authenticate a user. You can call the route anything you want apart from auth.

    Save the file. It will automatically refresh the running server instance.

    You should now see a login button on the top right of the screen when you access


    Clicking the button will redirect to a login template.

    Let's create a super-user for which to log in with.

    python3 createsuperuser

    Logging in should be a breeze with the username and password we just specified.

    Authorization: Adding permissions

    Right now, any user can view and edit any bucketlist. We'd want to tie the user to their bucketlist so that only the owner can effect changes like editing and deletion to it.

    A default permission check

    We can use the default permission package to restrict bucketlist access to authenticated users only.

    In we'll import the permission classes

    from rest_framework import permissions

    Then inside our CreateView class we'll add the permission class IsAuthenticated.

    # rest_api/
    class CreateView(generics.ListCreateAPIView):
        """This class handles the GET and POSt requests of our rest api."""
        queryset = Bucketlist.objects.all()
        serializer_class = BucketlistSerializer
        permission_classes = (permissions.IsAuthenticated,) # ADD THIS LINE

    The permission class IsAuthenticated will deny permission to any unauthenticated user, and allow permission otherwise. We could have used IsAuthenticatedOrReadOnly which permits unauthenticated users if the request is one of the "safe" methods (GET, HEAD and OPTIONS). But we want full security – we'll stick to IsAuthenticated.

    Custom Permission

    Right now, any authenticated user can see the other user's bucketlists. To implement the full concept of ownership, we'll have to create a custom permission.

    Let's create a file called inside the rest_api directory. Inside this file, we write the following code:

    from rest_framework.permissions import BasePermission
    from .models import Bucketlist
    class IsOwner(BasePermission):
        """Custom permission class to allow only bucketlist owners to edit them."""
        def has_object_permission(self, request, view, obj):
            """Return True if permission is granted to the bucketlist owner."""
            if isinstance(obj, Bucketlist):
                return obj.owner == request.user
            return obj.owner == request.user

    The class above implements a permission which holds by this truth – The user has to be the owner to have that object's permission. If they are indeed the owner of that bucketlist, it returns True, else False.

    We just have to add it inside our permission_classes tuple and we are set. For clarity, the updated should now look like this:

    # rest_api/
    from rest_framework import generics, permissions
    from .permissions import IsOwner
    from .serializers import BucketlistSerializer
    from .models import Bucketlist
    class CreateView(generics.ListCreateAPIView):
        """This class handles the GET and POSt requests of our rest api."""
        queryset = Bucketlist.objects.all()
        serializer_class = BucketlistSerializer
        permission_classes = (
            permissions.IsAuthenticated, IsOwner)
        def perform_create(self, serializer):
            """Save the post data when creating a new bucketlist."""
    class DetailsView(generics.RetrieveUpdateDestroyAPIView):
        """This class handles GET, PUT, PATCH and DELETE requests."""
        queryset = Bucketlist.objects.all()
        serializer_class = BucketlistSerializer
        permission_classes = (

    If we log out and try to get the bucketlists, we'll be hit by a HTTP 403 Forbidden response. This means that our authentication and authorization is actually working!


    Finally, we run our tests and see whether they'll pass:

    python3 test

    Moving on swiftly.

    What about Token-based Authentication?

    Token authentication is appropriate for client–server setups especially when the consumption clients are native desktop or native mobile.

    This is how it works – A user requests a security token from the server. The server generates the token and associates it with that user. After sending the token, the server waits for the user to request for resources using that specific token. The user can then use the token to authenticate and prove to the server that he/she is indeed a valid user.

    For us to use token authentication in our API, we'll have to set up some configurations on the file.

    Let's add rest_framework.authtoken in our list of installed apps – like this:

    # project/
        'rest_api', # note the comma (if you lack it, errors! the horror!)
        'rest_framework.authtoken' # ADD THIS LINE

    Every time we create a user, we'd like to also create a security token for them. But how do we ensure that a user creation will also trigger a token creation?

    Enter signals.

    Django comes packed with a signal dispatcher. A dispatcher is like a messanger sent forth to notify others about an event that just happened. When a user is created, a post_save signal will be emitted by the User model. A receiver(which is simply a function) will then help us catch this post_save signal and immediately create the token.

    Our receiver will live in our file. A couple of imports to add: the post_save signal, the default User model, the Token model and the receiver:

    from django.db.models.signals import post_save
    from django.contrib.auth.models import User
    from rest_framework.authtoken.models import Token
    from django.dispatch import receiver

    Then write the receiver at the bottom of the file like this:

    # rest_api/
    from django.db.models.signals import post_save
    from django.contrib.auth.models import User
    from rest_framework.authtoken.models import Token
    from django.dispatch import receiver
    class Bucketlist(models.Model):
        """This class represents the bucketlist model."""
        name = models.CharField(max_length=255, blank=False, unique=True)
        owner = models.ForeignKey(
        date_created = models.DateTimeField(auto_now_add=True)
        date_modified = models.DateTimeField(auto_now=True)
        def __str__(self):
            """Return a human readable representation of the model instance."""
            return "{}".format(
    # This receiver handles token creation immediately a new user is created.
    @receiver(post_save, sender=User)
    def create_auth_token(sender, instance=None, created=False, **kwargs):
        if created:

    Note that the receiver is NOT indented inside the Bucketlist model class. It's a common mistake to indent it inside the class.

    We also need to provide a way for the user to obtain the token. A url will serve the purpose. Write the following lines of code on the

    # rest_api/
    from rest_framework.authtoken.views import obtain_auth_token # add this import
    urlpatterns = {
        url(r'^bucketlists/$', CreateView.as_view(), name="create"),
            DetailsView.as_view(), name="details"),
        url(r'^auth/', include('rest_framework.urls',
        url(r'^users/$', UserView.as_view(), name="users"),
            UserDetailsView.as_view(), name="user_details"),
        url(r'^get-token/', obtain_auth_token), # Add this line
    urlpatterns = format_suffix_patterns(urlpatterns)

    The rest framework is so powerful that it provides a built-in view which handles obtaining the token when a user posts their username and password.

    We'll go ahead with making migrations and migrate the changes to the database so that our app can tap the power of this built-in view.

    python3 makemigrations &&  python3 migrate

    Finally, we add some configs to the settings so that our app can authenticate with both BasicAuthentication and TokenAuthentication.

    # project/

    The DEFAULT_AUTHENTICATION_CLASSES config tells the app that we wish to configure more than one ways of authenticating the user. We specify the ways by referencing the built-in authentication classes inside this tuple.

    Run it

    Once saved, the server will automagically restart with the added changes if it's already running. However, it's good to just rerun the server with python3 runserver

    To visually test whether our api still stands, we'll make the HTTP requests on Postman

    Postman Step 1: Obtain that token

    For clients to authenticate, the token obtained should be included in the Authorization HTTP header. We prepend the word Token followed by a space character. The header should look like this:

    Authorization: Token 2777b09199c62bcf9418ad846dd0e4bbdfc6ee4b

    Don't forget to put the space in between.

    We'll make a post request to http://localhost:8000/get-token/, specifying the username and password in the process.

    Postman Step 2: Use obtained token in Authorization header

    For the subsequent requests, we'll have to include the Authorization header if we ever want to access the API resources.

    A common mistake that might cause errors here is inputing an incorrect format for the Authorization header. Here's a common error message from the server:

      "detail": "Authentication credentials were not provided."

    Ensure you input this format instead– Token <your-new-token-is-here>. If you want to have a different keyword in the header, such as Bearer, simply subclass TokenAuthentication and set the keyword class variable.

    Let's try sending a GET request. It should yield something like this: Feel free to play around with your now well secured API.


    If you've read this to the end, you are awesome!

    We've covered quite a lot! From implementing user authentication to creating custom permissions for implementing authorization, we've covered most of securing a Django API.

    We also conveniently defined a token-based authentication layer so that mobile and desktop clients can securely consume our API. But the most important thing is that we refactored our tests to accomodate the changes. This is paramount to anything else and remains the heart of Test Driven Development.

    If you get to that point where you ask yourself, "what is going on here?", I highly recommend you take a look at Part 1 of this series which aptly provides a detailed tutorial on building a bucketlist API the TDD way.

    Happy coding!

    Jee Gikera

    5 posts

    A full-stack software engineer, Jee believes in doing it today, not tomorrow. He enjoys gaming, lively hangouts with friends and family, and when away – the quest for rare coins.