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)
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)
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/
This will handle basic authenticated API requests, without any front end.