REST API with Django

29 Mar, 2018

Categories back end dev

Building a Django REST API

Setup

mkdir django-api && cd django-api
virtualenv apiEnv
source apiEnv/bin/activate
pip install django django-rest-framework
touch requirements.txt
pip feeze > requirements.txt
django-admin startproject base

Add django-rest-framework to base/base/settings.py:

...

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # Add this line
]
cd base
python manage.py startapp api

Edit base/settings.py again:

...

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'api', # Add this line
]

api/models.py:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# Create your models here.
class Project(models.Model):
    """This class represents the bucketlist model."""
    name = models.CharField(max_length=255, blank=False, unique=True)
    type = models.CharField(max_length=255, blank=False, unique=False)
    clientCo = models.CharField(max_length=255, blank=False, unique=False)
    clientEmail =  models.CharField(max_length=255, blank=False, unique=False)
    currentDate = models.CharField(max_length=255, blank=False, unique=False)
    startDate = models.CharField(max_length=255, blank=False, unique=False)
    endDate = models.CharField(max_length=255, blank=False, unique=False)
    hours = models.CharField(max_length=255, blank=False, unique=False)
    rate = models.CharField(max_length=255, blank=False, unique=False)
    currency = models.CharField(max_length=255, blank=False, unique=False)
    status = models.CharField(max_length=255, blank=False, unique=False)

    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(self.name)

api/tests.py:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.test import TestCase
from .models import Project
from rest_framework.test import APIClient
from rest_framework import status
from django.core.urlresolvers import reverse

# Create your tests here.
class ModelTestCase(TestCase):
    """This class defines the test suite for the project model."""

    def setUp(self):
        """Define the test client and other test variables."""
        self.project_name = "Write world class code"
        self.project = Project(name=self.project_name)

    def test_model_can_create_a_project(self):
        """Test the project model can create a project."""
        old_count = Project.objects.count()
        self.project.save()
        new_count = Project.objects.count()
        self.assertNotEqual(old_count, new_count)

Set up Django Rest Framework

Django Rest Framework comes with built-in serializers that translate the models into JSON to be returned by the API. Add a serializers file and define a serializer for the Project model.

api/serializers.py:

from rest_framework import serializers
from .models import Project

class ProjectSerializer(serializers.ModelSerializer):
    """Serializer to map the Model instance into JSON format."""

    class Meta:
        """Meta class to map serializer's fields with the model fields."""
        model = Project
        fields = ('id', 'name', 'date_created', 'date_modified')
        read_only_fields = ('date_created', 'date_modified')

Create two views that we can call when a user hits a specific URL. Define CreateView, a DRF built-in to see and update objects via the API; and DetailsView, which returns an object by id.

api/views.py:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.shortcuts import render
from rest_framework import generics
from .serializers import ProjectSerializer
from .models import Project

class CreateView(generics.ListCreateAPIView):
    """This class defines the create behavior of our rest api."""
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer

    def perform_create(self, serializer):
        """Save the post data when creating a new project."""
        serializer.save()

class DetailsView(generics.RetrieveUpdateDestroyAPIView):
    """This class handles the http GET, PUT and DELETE requests."""

    queryset = Project.objects.all()
    serializer_class = ProjectSerializer

Update api's urlconf to route to views.

api/urls.py:

# api/urls.py

from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from .views import CreateView, DetailsView

urlpatterns = {
    url(r'^projects/$', CreateView.as_view(), name="create"),
    url(r'^projects/(?P<pk>[0-9]+)/$',
        DetailsView.as_view(), name="details"),
}

urlpatterns = format_suffix_patterns(urlpatterns)

Update the main (base) app's urlconf to route to api's urlconf.

base/urls.py:

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('api.urls')) # Add this line
]

Add API tests to api/tests.py:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.test import TestCase
from .models import Project

# Create your tests here.
class ModelTestCase(TestCase):
    """This class defines the test suite for the project model."""

    def setUp(self):
        """Define the test client and other test variables."""
        self.project_name = "Write world class code"
        self.project = Project(name=self.project_name)

    def test_model_can_create_a_project(self):
        """Test the project model can create a project."""
        old_count = Project.objects.count()
        self.project.save()
        new_count = Project.objects.count()
        self.assertNotEqual(old_count, new_count)

# API View Tests
class ViewTestCase(TestCase):
    """Test suite for the api views."""

    def setUp(self):
        """Define the test client and other test variables."""
        self.client = APIClient()
        self.project_data = {'name': 'Go to Ibiza'}
        self.response = self.client.post(
            reverse('create'),
            self.project_data,
            format="json")

    def test_api_can_create_a_project(self):
        """Test the api has bucket creation capability."""
        self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)


    def test_api_can_get_a_project(self):
            """Test the api can get a given project."""
            project = Project.objects.get()
            response = self.client.get(
                reverse('details',
                kwargs={'pk': project.id}), format="json")

            self.assertEqual(response.status_code, status.HTTP_200_OK)
            self.assertContains(response, project)

    def test_api_can_update_project(self):
        """Test the api can update a given project."""
        change_project = {'name': 'Something new'}
        res = self.client.put(
            reverse('details', kwargs={'pk': project.id}),
            change_project, format='json'
        )
        self.assertEqual(res.status_code, status.HTTP_200_OK)

    def test_api_can_delete_project(self):
        """Test the api can delete a project."""
        project = Project.objects.get()
        response = self.client.delete(
            reverse('details', kwargs={'pk': project.id}),
            format='json',
            follow=True)

        self.assertEquals(response.status_code, status.HTTP_204_NO_CONTENT)

Adding Authentification

Add a foreignkey owner to the Project model in api/models.py:

owner = models.ForeignKey('auth.User', related_name='projects',  on_delete=models.CASCADE)

Make migrations and restart development server. After registering a user via DRF's auth views, we can use tokens to ensure easier access. We have to hit endpoint that will generate a token with a POST request that contains a username and password.

curl -X POST -d "username=<username>&password=<password>" http://localhost:8000/get-token/

That will generate and return a new token.

curl -X GET -H "Authorization: Token <your-token>" http://localhost:8000/projects/

Conclusion

This will handle basic authenticated API requests, without any front end.



Back