The purpose of this article is to help you learn new staff and practicies while creating new things. The goal is to create a To-do List application where we can store our thoughts about things we want to do for today, tomorrow or someday.
- It is used and trusted by internationally recognised companies including Mozilla, Red Hat, Heroku, and Eventbrite.
- It's Customizable all the way down
- The huge usability of the web browsable API
- Serialization that supports both ORM and non-ORM data sources
- Extensive documentation and huge community support
You can read more about the Django REST Framework here
For the Django REST Framework we need Python and Django Web Framework
Let's go ahead and install the requirements
First things First!
Create a folder for you project, I will name mine 'todolist'
$ mkdir todolist
Then create a virtual environment for this project using virtualenv and Python 3.
The use of virtual environment is strongly recommended
$ virtualenv -p /usr/local/bin/python3 venv
Now that we have created our environment there is one last thing we need to do before any installation attempt. Activate it
$ source venv/bin/activate
You'll see your prompt with a (venv) prefix, like this:
(venv) pycat@ghost:~/workspace/APIs/todolist$
Now we are ready ti install the dependencies. Lets' install Django using pip :
$ pip install Django
And then finaly create the our project. You can give a whatever name to your project
$ django-admin startproject todorest .
* dont forget the dot at the end
Now Django already structered our basic project file witch looks like this
todorest │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py
There is one more requirement we need to install - DRF
$ pip install djangorestframework
Include DRF into settings file:
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', ]
We will create a new django application with the name app. Yeah, that's right
the REST API will get started as a simple django application
$ ./manage.py startapp api
Include api into settings file:
# 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', ]
The best way to do code testing is by using Test-Driven Development (TDD). This is how it works:
Write a test. – The test will flesh out some functionality in your app
Then, run the test – The test should fail, since there's no code to make it pass.
Write the code – To make the test pass
Run the test – If it passes, you are confident that the code you've written meets the test requirements
Refactor code – Remove duplication, prune large objects and make the code more readable. Re-run the tests every time you refactor the code
Repeat – That's it!
Open the test.py file inside the appi directory and add the following code.
The following code imports the testcase from django.test library and tests
whether the the Model TodoList can create a list. It is a simple test:
from django.test import TestCase from .models import TodoList class ModelTestCase(TestCase): # lets define a test suit for the todolist model def setUp(self): # define the test client and other test self.todolist_name = "Goto the supermarket" self.todolist = TodoList(name="todolist_name") def test_model_can_create_a_list(self): objects_count = TodoList.objects.count() self.todolist.save() objects_new_count = TodoList.objects.count() self.assertNotEqual(objects_count, objects_new_count)
We need to define the Model before we run the above test. Open the models.py file and add the folowing code:
from django.db import models
# Create your models here.
class TodoList(models.Model):
pass
Now it is time to run the tests.
(venv) pycat@ghost:~/workspace/APIs/todolist$ ./manage.py test
After that you will get nothing but errors but don't worry, that;s excactly the point of Test Driven Development
We will build the Model code while we're trying to solve the test errors ;)
Edit the models.py file and add the following:
from django.db import models # Create your models here. class TodoList(models.Model): name = models.CharField(max_length=255, blank=False, unique=True) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) def __str__(self): return "{}".format(self.name)
Migrate:
$ ./manage.py makemigrations
$ ./manage.py migrate
And run the test again:
$./manage.py tests
Bingo! This test passes!!!
Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON
, XML
or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data. You can read more about DRF Serializers here
The ModelSerializer
class provides a shortcut that lets you automatically create a Serializer
class with fields that correspond to the Model fields. Create a file serializers.py and fill in the following code:
from rest_framework import serializers from .models import TodoList class TodoListSerializer(serializers.ModelSerializer): class Meta: model = TodoList fields = ('id', 'name', 'created', 'modified') read_only_fields = ('created', 'modified')
That's it. This serializer maps the Model instance into JSON format!
First write the tests. Writing the tests seems scary at first but not if toy work according to strategy. How do you know what to test? Just think about and make a list of things that you want to implement. For the views we need to handle the follwing:
♦ Create a todo list – POST request
♦ Read a todo list – GET request
♦ Update a todo list – PUT request
♦ Delete a todo list – DELETE request
We're going now to write the test basing to the above list.
Add the code below in your tests.py :
# test views imports from rest_framework.test import APIClient from rest_framework import status from django.core.urlresolvers import reverse # define the ViewTestCase testsuite right after the ModelTestCase class ViewTestCase(TestCase): def setUp(self): self.client = APIClient() self.todolist_data = {'name': 'Go to the supermarket'} self.response = self.client.post( reverse('create'), self.todolist_data, format="json" ) def test_api_can_create_list(self): self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)
If you go ahaid you'll se tha running the test fails. That's ok because we havent implement yet the URconf and the views that satisfy this request. So lets implement them
Edit the views.py
from django.shortcuts import render from rest_framework import generics from .serializers import TodoListSerializer from .models import TodoList # Create your views here. class CreateView(generics.ListCreateAPIView): qyeryset = TodoList.objects.all() serializer_class = TodoListSerializer def create_performance(self,serializer): # save the POST data when user creates a new todo list serializer.save()
We make use of the ListCreateAPIVIew witch is a DRF class-based view that provides GET and POST handlers. One of the key benefits of class-based views is the way they allow you to compose bits of reusable behavior. REST framework takes advantage of this by providing a number of pre-built views that provide for commonly used patterns. You can read more about DRF Generic Views here.
URLs are the interface of our API to the outside world. A clean and usable URL scheme is an important detail in a high-quality Wev Application. With DRF we can design URLs however we want with no framework limitations.
We will create a urls.py
file on the api directory. This is where we define our url patterns.
from django.conf.urls import url, include from .views import CreateView urlpatterns = { url(r'^todolist/$', CreateView.as_view(), name="create") }
We'll also have to include our api.urls to the main apps urls so tha it points to our
API. Go to the todorest folder edit the urls.py. Our urls.py
now should look
like this:
from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), # finaly we add the url to the main app's urls.py so that it point to the APIapp url(r'^', include('api.urls')), ]
(venv) pycat@ghost:~/workspace/APIs/todolist$ ./manage.py test Creating test database for alias 'default'... .. ---------------------------------------------------------------------- Ran 2 tests in 0.017s OK Destroying test database for alias 'default'...
$ ./manage.py runserver
We assume here that we have allready activated a virtualenv with python3. It's the same typing:
$ python3 manage.py runserver
If we see the following console means that everything goes well and the app is running smoothly
(venv) pycat@ghost:~/workspace/APIs/todolist$ ./manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
April 04, 2017 - 09:33:08
Django version 1.10.6, using settings 'todorest.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Now let's open the todo list url on the browser to check out how it works. Type this or click it to open http://localhost:8000/todolist/
If everything has gone well you should see something similar to this:
Time check how the api works. Go ahead and insert a Todo List. Then you should see something like this:
Test goes First as usual:
def test_api_can_get_list(self): todolist = TodoList.objects.all() response = self.client.get( reverse('details'), kwargs = {'pk': todolist.id}, format="json" ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertContains(response, todolist) def test_api_can_update_list(self): todolist = TodoList.objects.get() update_list = {'name': 'New list item name'} response = self.client.put( reverse('details', kwargs={'pk': todolist.id}), update_list, format='json' ) self.assertEqual(response.status_code, status.HTTP_200_OK) def test_api_can_delete(self): todolist = TodoList.objects.get() response = self.client.delete( reverse('details', kwargs={'pk': todolist.id}), format='json', follow=True) self.assertEquals(response.status_code, status.HTTP_204_NO_CONTENT)
Running this tests we get 3 fails. That's absolute normal. We need to write some code in order get rid of those test errors. If we pay some attention on the erro logging we will find out that threre are some view problems and also some url resorvers that need configuration.
First we will implement our details view using RetrieveUpdateDestroyAPIView a Generic view witch is used for read-write-delete endpoints to represent a single model instance. It Provides GET, PUT
, PATCH
and DELETE method handlers. You can read more in the documentation
Add the following code in your views.py
class DetailsView(generics.RetrieveUpdateDestroyAPIView): queryset = TodoList.objects.all() serializer_class = TodoListSerializer
Now add the url configuration for this view. Our urls.py now should look like this:
from django.conf.urls import url, include from .views import CreateView DetailsView urlpatterns = { url(r'^todolist/$', CreateView.as_view(), name="create") url(r'^todolist/(?P<pk>[0-9]+)/$',DetailsView.as_view(), name="details"), }
That's It!!
In software development, testing is paramount. So why should I do it, you ask?
Tests have a short feedback loop, enabling you and your team to learn faster and adjust
Less time is spent debugging, allowing you to spend more time writing code
Tests act as documentation for your code!
They improve code quality while reducing bugs
After refactoring code, your tests will tell you whether the change has broken previously working code, and...
Tests are documentation Ever suffered from not knowing how a newly acquired and quite complex system works? Well-written unit tests show how the individual classes work together to achieve one or more business tasks. Analysing that code permitts new developers figure out the internal workings of the system quickly.
Unit tests help you to get zoned. Programmers and HR managers dream of the concept of the zone – a mental state where effectiveness reaches the highest possible level. Getting into it tends to require some introductory work, which is subject related but not particularly complex. Coding unit tests is an ideal example for such ‘menial tasks
It prevents catastrophic errors If you release an application where a crucial feature doesn’t work it can result in huge monetal losses. A reliable set of tests mitigates this by ensuring that no products get shippeThis is how it works:
Testing Is Fun. If you thrive on challenges, then testing is a lot of fun.