Building RESTful Python Web Services

4.8 (9 reviews total)
By Gaston C. Hillar
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Developing RESTful APIs with Django

About this book

Python is the language of choice for millions of developers worldwide, due to its gentle learning curve as well as its vast applications in day-to-day programming. It serves the purpose of building great web services in the RESTful architecture. This book will show you the best tools you can use to build your own web services.

Learn how to develop RESTful APIs using the popular Python frameworks and all the necessary stacks with Python, Django, Flask, and Tornado, combined with related libraries and tools. We will dive deep into each of these frameworks to build various web services, and will provide use cases and best practices on when to use a particular framework to get the best results.

We will show you everything required to successfully develop RESTful APIs with the four frameworks such as request handling, URL mapping, serialization, validation, authentication, authorization, versioning, ORMs, databases, custom code for models and views, and asynchronous callbacks. At the end of each framework, we will add authentication and security to the RESTful APIs and prepare tests for it.

By the end of the book, you will have a deep understanding of the stacks needed to build RESTful web services.

Publication date:
October 2016
Publisher
Packt
Pages
418
ISBN
9781786462251

 

Chapter 1. Developing RESTful APIs with Django

In this chapter, we will start our journey towards RESTful Web APIs with Python and four different Web frameworks. Python is one of the most popular and versatile programming languages. There are thousands of Python packages, which allow you to extend Python capabilities to any kind of domain you can imagine. We can work with many different Web frameworks and packages to easily build simple and complex RESTful Web APIs with Python, and we can also combine these frameworks with other Python packages.

We can leverage our existing knowledge of Python and its packages to code the different pieces of our RESTful Web APIs and their ecosystem. We can use the object-oriented features to create code that is easier to maintain, understand, and reuse. We can use all the packages that we already know to interact with databases, Web services, and different APIs. Python makes it easy for us to create RESTful Web APIs. We don't need to learn another programming language; we can use the one we already know and love.

In this chapter, we will start working with Django and Django REST Framework, and we will create a RESTful Web API that performs CRUD (Create, Read, Update, and Delete) operations on a simple SQLite database. We will:

  • Design a RESTful API to interact with a simple SQLite database

  • Understand the tasks performed by each HTTP method

  • Set up the virtual environment with Django REST framework

  • Create the database models

  • Manage serialization and deserialization of data

  • Write API views

  • Make HTTP requests to the API with command-line tools

  • Work with GUI tools to compose and send HTTP requests

 

Designing a RESTful API to interact with a simple SQLite database


Imagine that we have to start working on a mobile App that has to interact with a RESTful API to perform CRUD operations with games. We don't want to spend time choosing and configuring the most appropriate ORM ( Object-Relational Mapping); we just want to finish the RESTful API as soon as possible to start interacting with it via our mobile App. We really want the games to persist in a database but we don't need it to be production-ready, and therefore, we can use the simplest possible relational database, as long as we don't have to spend time making complex installations or configurations.

Django REST framework, also known as DRF, will allow us to easily accomplish this task and start making HTTP requests to our first version of our RESTful Web Service. In this case, we will work with a very simple SQLite database, the default database for a new Django REST framework project.

First, we must specify the requirements for our main resource: a game. We need the following attributes or fields for a game:

  • An integer identifier

  • A name or title

  • A release date

  • A game category description, such as 3D RPG and 2D mobile arcade.

  • A bool value indicating whether the game was played at least once by a player or not

In addition, we want our database to save a timestamp with the date and time in which the game was inserted in the database.

The following table shows the HTTP verbs, the scope, and the semantics for the methods that our first version of the API must support. Each method is composed by an HTTP verb and a scope and all the methods have a well defined meaning for all games and collections.

HTTP verb

Scope

Semantics

GET

Collection of games

Retrieve all the stored games in the collection, sorted by their name in ascending order

GET

Game

Retrieve a single game

POST

Collection of games

Create a new game in the collection

PUT

Game

Update an existing game

DELETE

Game

Delete an existing game

Tip

In a RESTful API, each resource has its own unique URL. In our API, each game has its own unique URL.

 

Understanding the tasks performed by each HTTP method


In the preceding table, the GET HTTP verb appears twice but with two different scopes. The first row shows a GET HTTP verb applied to a collection of games (collection of resources) and the second row shows a GET HTTP verb applied to a game (a single resource).

Let's consider that http://localhost:8000/games/ is the URL for the collection of games. If we add a number and a slash (/) to the preceding URL, we identify a specific game whose id or primary key is equal to the specified numeric value. For example, http://localhost:8000/games/12/ identifies the game whose id or primary key is equal to 12.

We have to compose and send an HTTP request with the following HTTP verb (POST) and request URL (http://localhost:8000/games/) to create a new game. In addition, we have to provide the JSON (JavaScript Object Notation) key-value pairs with the field names and the values to create the new game. As a result of the request, the server will validate the provided values for the fields, make sure that it is a valid game and persist it in the database.

The server will insert a new row with the new game in the appropriate table and it will return a 201 Created status code and a JSON body with the recently added game serialized to JSON, including the assigned id or primary key that was automatically generated by the database and assigned to the game object.

POST http://localhost:8000/games/ 

We have to compose and send an HTTP request with the following HTTP verb (GET) and request URL (http://localhost:8000/games/{id}/) to retrieve the game whose id or primary key matches the specified numeric value in the place where {id} is written.

For example, if we use the request URL http://localhost:8000/games/50/, the server will retrieve the game whose id or primary key matches 50.

As a result of the request, the server will retrieve a game with the specified id or primary key from the database and create the appropriate game object in Python. If a game is found, the server will serialize the game object into JSON and return a 200 OK status code and a JSON body with the serialized game object. If no game matches the specified id or primary key, the server will return just a 404 Not Found status:

GET http://localhost:8000/games/{id}/ 

We have to compose and send an HTTP request with the following HTTP verb (PUT) and request URL (http://localhost:8000/games/{id}/) to retrieve the game whose id or primary key matches the specified numeric value in the place where {id} is written and replace it with a game created with the provided data. In addition, we have to provide the JSON key-value pairs with the field names and the values to create the new game that will replace the existing one. As a result of the request, the server will validate the provided values for the fields, make sure that it is a valid game and replace the one that matches the specified id or primary key with the new one in the database. The id or primary key for the game will be the same after the update operation. The server will update the existing row in the appropriate table and it will return a 200 OK status code and a JSON body with the recently updated game serialized to JSON. If we don't provide all the necessary data for the new game, the server will return a 400 Bad Request status code. If the server doesn't find a game with the specified id, the server will return just a 404 Not Found status.

PUT http://localhost:8000/games/{id}/ 

We have to compose and send an HTTP request with the following HTTP verb (DELETE) and request URL (http://localhost:8000/games/{id}/) to remove the game whose id or primary key matches the specified numeric value in the place where {id} is written. For example, if we use the request URL http://localhost:8000/games/20/, the server will delete the game whose id or primary key matches 20. As a result of the request, the server will retrieve a game with the specified id or primary key from the database and create the appropriate game object in Python. If a game is found, the server will request the ORM to delete the game row associated with this game object and the server will return a 204 No Content status code. If no game matches the specified id or primary key, the server will return just a 404 Not Found status.

DELETE http://localhost:8000/games/{id}/ 
 

Working with lightweight virtual environments


Throughout this book, we will be working with different frameworks and libraries, and therefore, it is convenient to work with virtual environments. We will work with the lightweight virtual environments introduced in Python 3.3 and improved in Python 3.4. However, you can also choose to use the popular virtualenv (https://pypi.python.org/pypi/virtualenv) third-party virtual environment builder or the virtual environment options provided by your Python IDE.

You just have to make sure that you activate your virtual environment with the appropriate mechanism when it is necessary to do so, instead of following the step explained to activate the virtual environment generated with the venv module integrated in Python. You can read more information about PEP 405 Python Virtual Environment that introduced the venv module at https://www.python.org/dev/peps/pep-0405.

Tip

Each virtual environment we create with venv is an isolated environment and it will have its own independent set of installed Python packages in its site directories. When we create a virtual environment with venv in Python 3.4 and greater, pip is included in the new virtual environment. In Python 3.3, it was necessary to manually install pip after creating the virtual environment. Notice that the instructions provided are compatible with Python 3.4 or greater, including Python 3.5.x. The following commands assume that you have Python 3.5.x installed on macOS, Linux, or Windows.

First, we have to select the target folder or directory for our virtual environment. The following is the path we will use in the example for macOS and Linux. The target folder for the virtual environment will be the PythonREST/Django folder within our home directory. For example, if our home directory in macOS or Linux is /Users/gaston, the virtual environment will be created within /Users/gaston/PythonREST/Django. You can replace the specified path with your desired path in each command.

    ~/PythonREST/Django  

The following is the path we will use in the example for Windows. The target folder for the virtual environment will be the PythonREST/Django folder within our user profile folder. For example, if our user profile folder is C:\Users\Gaston, the virtual environment will be created within C:\Users\gaston\PythonREST\Django. You can replace the specified path with your desired path in each command.

%USERPROFILE%\PythonREST\Django

Now, we have to use the -m option followed by the venv module name and the desired path to make Python run this module as a script and create a virtual environment in the specified path. The instructions are different depending on the platform in which we are creating the virtual environment.

Open a Terminal in macOS or Linux and execute the following command to create a virtual environment:

python3 -m venv ~/PythonREST/Django01

In Windows, execute the following command to create a virtual environment:

python -m venv %USERPROFILE%\PythonREST\Django01

The preceding command doesn't produce any output. The script created the specified target folder and installed pip by invoking ensurepip because we didn't specify the --without-pip option. The specified target folder has a new directory tree that contains Python executable files and other files that indicate that it is a virtual environment.

The pyenv.cfg configuration file specifies different options for the virtual environment and its existence is an indicator that we are in the root folder for a virtual environment. In OS and Linux, the folder will have the following main sub-folders—bin, include, lib, lib/python3.5 and lib/python3.5/site-packages. In Windows, the folder will have the following main sub-folders—Include, Lib, Lib\site-packages, and Scripts. The directory trees for the virtual environment in each platform are the same as the layout of the Python installation in these platforms. The following screenshot shows the folders and files in the directory trees generated for the Django01 virtual environment in macOS:

The following screenshot shows the main folders in the directory trees generated for the virtual environments in Windows:

Tip

After we activate the virtual environment, we will install third-party packages into the virtual environment and the modules will be located within the lib/python3.5/site-packages or Lib\site-packages folder, based on the platform. The executables will be copied in the bin or Scripts folder, based on the platform. The packages we install won't make changes to other virtual environments or our base Python environment.

Now that we have created a virtual environment, we will run a platform-specific script to activate it. After we activate the virtual environment, we will install packages that will only be available in this virtual environment.

Run the following command in the terminal in macOS or Linux. Note that the results of this command will be accurate if you don't start a different shell than the default shell in the terminal session. In case you have doubts, check your terminal configuration and preferences.

echo $SHELL

The command will display the name of the shell you are using in the Terminal. In macOS, the default is /bin/bash and this means you are working with the bash shell. Depending on the shell, you must run a different command to activate the virtual environment in OS or Linux.

If your Terminal is configured to use the bash shell in macOS or Linux, run the following command to activate the virtual environment. The command also works for the zsh shell:

source ~/PythonREST/Django01/bin/activate

If your Terminal is configured to use either the csh or tcsh shell, run the following command to activate the virtual environment:

source ~/PythonREST/Django01/bin/activate.csh

If your Terminal is configured to use either the fish shell, run the following command to activate the virtual environment:

source ~/PythonREST/Django01/bin/activate.fish

In Windows, you can run either a batch file in the command prompt or a Windows PowerShell script to activate the virtual environment. If you prefer the command prompt, run the following command in the Windows command line to activate the virtual environment:

%USERPROFILE%\PythonREST\Django01\Scripts\activate.bat

If you prefer the Windows PowerShell, launch it and run the following commands to activate the virtual environment. However, notice that you should have scripts execution enabled in Windows PowerShell to be able to run the script:

cd $env:USERPROFILE
PythonREST\Django01\Scripts\Activate.ps1

After you activate the virtual environment, the command prompt will display the virtual environment root folder name enclosed in parenthesis as a prefix of the default prompt to remind us that we are working in the virtual environment. In this case, we will see (Django01) as a prefix for the command prompt because the root folder for the activated virtual environment is Django01.

The following screenshot shows the virtual environment activated in a macOS El Capitan terminal with a bash shell, after executing the previously shown commands:

As we can see in the preceding screenshot, the prompt changed from Gastons-MacBook-Pro:~ gaston$ to (Django01) Gastons-MacBook-Pro:~ gaston$ after the activation of the virtual environment.

The following screenshot shows the virtual environment activated in a Windows 10 Command Prompt, after executing the previously shown commands:

As we can notice from the preceding screenshot, the prompt changed from C:\Users\gaston\AppData\Local\Programs\Python\Python35 to (Django01) C:\Users\gaston\AppData\Local\Programs\Python\Python35 after the activation of the virtual environment.

Tip

It is extremely easy to deactivate a virtual environment generated with the previously explained process. In macOS or Linux, just type deactivate and press Enter. In a Windows command prompt, you have to run the deactivate.bat batch file included in the Scripts folder (%USERPROFILE%\PythonREST\Django01\Scripts\deactivate.bat in our example). In Windows PowerShell, you have to run the Deactivate.ps1 script in the Scripts folder. The deactivation will remove all the changes made in the environment variables.

 

Setting up the virtual environment with Django REST framework


We have created and activated a virtual environment. It is time to run many commands that will be the same for either macOS, Linux or Windows. Now, we must run the following command to install the Django Web framework:

pip install django

The last lines of the output will indicate that the django package has been successfully installed. Take into account that you may also see a notice to upgrade pip.

Collecting django
Installing collected packages: django
Successfully installed django-1.10

Now that we have installed Django Web framework, we can install Django REST framework. We just need to run the following command to install this package:

pip install djangorestframework

The last lines for the output will indicate that the djangorestframework package has been successfully installed:

Collecting djangorestframework
Installing collected packages: djangorestframework
Successfully installed djangorestframework-3.3.3

Go to the root folder for the virtual environment-Django01. In macOS or Linux, enter the following command:

cd ~/PythonREST/Django01

In Windows, enter the following command:

cd /d %USERPROFILE%\PythonREST\Django01

Run the following command to create a new Django project named gamesapi. The command won't produce any output:

django-admin.py startproject gamesapi

The previous command created a gamesapi folder with other sub-folders and Python files. Now, go to the recently created gamesapi folder. Just execute the following command:

cd gamesapi

Then, run the following command to create a new Django app named games within the gamesapi Django project. The command won't produce any output:

python manage.py startapp games

The previous command created a new gamesapi/games sub-folder, with the following files:

  • __init__.py

  • admin.py

  • apps.py

  • models.py

  • tests.py

  • views.py

In addition, the gamesapi/games folder will have a migrations sub-folder with an __init__.py Python script. The following diagram shows the folders and files in the directory trees starting at the gamesapi folder:

Let's check the Python code in the apps.py file within the gamesapi/games folder. The following lines shows the code for this file:

from django.apps import AppConfig 
 
 
class GamesConfig(AppConfig): 
    name = 'games' 

The code declares the GamesConfig class as a subclass of the django.apps.AppConfig class that represents a Django application and its configuration. The GamesConfig class just defines the name class attribute and sets its value to 'games'. We have to add games.apps.GamesConfig as one of the installed apps in the gamesapi/settings.py file that configures settings for the gamesapi Django project. We built the preceding string as follows-app name + .apps. + class name, which is, games + .apps. + GamesConfig. In addition, we have to add the rest_framework app to make it possible for us to use Django REST Framework.

The gamesapi/settings.py file is a Python module with module-level variables that define the configuration of Django for the gamesapi project. We will make some changes to this Django settings file. Open the gamesapi/settings.py file and locate the following lines that specify the strings list that declares the installed apps:

INSTALLED_APPS = [ 
    'django.contrib.admin', 
    'django.contrib.auth', 
    'django.contrib.contenttypes', 
    'django.contrib.sessions', 
    'django.contrib.messages', 
    'django.contrib.staticfiles', 
] 

Add the following two strings to the INSTALLED_APPS strings list and save the changes to the gamesapi/settings.py file:

  • 'rest_framework'

  • 'games.apps.GamesConfig'

The following lines show the new code that declares the INSTALLED_APPS strings list with the added lines highlighted. The code file for the sample is included in the restful_python_chapter_01_01 folder:

INSTALLED_APPS = [ 
    'django.contrib.admin', 
    'django.contrib.auth', 
    'django.contrib.contenttypes', 
    'django.contrib.sessions', 
    'django.contrib.messages', 
    'django.contrib.staticfiles', 
    # Django REST Framework 
    'rest_framework', 
    # Games application 
    'games.apps.GamesConfig', 
] 

This way, we have added Django REST Framework and the games application to our initial Django project named gamesapi.

 

Creating the models


Now, we will create a simple Game model that we will use to represent and persist games. Open the games/models.py file. The following lines show the initial code for this file, with just one import statement and a comment that indicates we should create the models:

from django.db import models 
 
# Create your models here. 

The following lines show the new code that creates a Game class, specifically, a Game model in the games/models.py file. The code file for the sample is included in the restful_python_chapter_01_01 folder:

from django.db import models 
 
 
class Game(models.Model): 
    created = models.DateTimeField(auto_now_add=True) 
    name = models.CharField(max_length=200, blank=True, default='') 
    release_date = models.DateTimeField() 
    game_category = models.CharField(max_length=200, blank=True, default='') 
    played = models.BooleanField(default=False) 
 
    class Meta: 
        ordering = ('name',) 

The Game class is a subclass of the django.db.models.Model class. Each defined attribute represents a database column or field. Django automatically adds an auto-increment integer primary key column named id when it creates the database table related to the model. However, the model maps the underlying id column in an attribute named pk for the model. We specified the field types, maximum lengths and defaults for many attributes. The class declares a Meta inner class that declares a ordering attribute and sets its value to a tuple of string whose first value is the 'name' string, indicating that, by default, we want the results ordered by the name attribute in ascending order.

Then, it is necessary to create the initial migration for the new Game model we recently coded. We just need to run the following Python scripts and we will also synchronize the database for the first time. By default, Django uses an SQLite database. In this example, we will be working with this default configuration:

python manage.py makemigrations games

The following lines show the output generated after running the preceding command.

Migrations for 'games':
  0001_initial.py:
    - Create model Game

The output indicates that the gamesapi/games/migrations/0001_initial.py file includes the code to create the Game model. The following lines show the code for this file that was automatically generated by Django. The code file for the sample is included in the restful_python_chapter_01_01 folder:

# -*- coding: utf-8 -*- 
# Generated by Django 1.9.6 on 2016-05-17 21:19 
from __future__ import unicode_literals 
 
from django.db import migrations, models 
 
 
class Migration(migrations.Migration): 
 
    initial = True 
 
    dependencies = [ 
    ] 
 
    operations = [ 
        migrations.CreateModel( 
            name='Game', 
            fields=[ 
                ('id', models.AutoField(auto_created=True, primary_key=True,
                serialize=False, verbose_name='ID')), 
                ('created', models.DateTimeField(auto_now_add=True)), 
                ('name', models.CharField(blank=True, default='',
                 max_length=200)), 
                ('release_date', models.DateTimeField()), 
                ('game_category', models.CharField(blank=True, default='',
                 max_length=200)), 
                ('played', models.BooleanField(default=False)), 
            ], 
            options={ 
                'ordering': ('name',), 
            }, 
        ), 
    ] 

The code defines a subclass of the django.db.migrations.Migration class named Migration that defines an operation that creates the Game model's table. Now, run the following python script to apply all the generated migrations:

python manage.py migrate

The following lines show the output generated after running the preceding command:

Operations to perform:
  Apply all migrations: sessions, games, contenttypes, admin, auth
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying games.0001_initial... OK
  Applying sessions.0001_initial... OK

After we run the preceding command, we will notice that the root folder for our gamesapi project now has a db.sqlite3 file. We can use the SQLite command line or any other application that allows us to easily check the contents of the SQLite database to check the tables that Django generated.

In macOS and most modern Linux distributions, SQLite is already installed, and therefore, you can run the sqlite3 command-line utility. However, in Windows, if you want to work with the sqlite3.exe command-line utility, you will have to download and install SQLite from its Web page-http://www.sqlite.org.

Run the following command to list the generated tables:

sqlite3 db.sqlite3 '.tables'

Run the following command to retrieve the SQL used to create the games_game table:

sqlite3 db.sqlite3 '.schema games_game'

The following command will allow you to check the contents of the games_game table after we compose and send HTTP requests to the RESTful API and make CRUD operations to the games_game table:

sqlite3 db.sqlite3 'SELECT * FROM games_game ORDER BY name;'

Instead of working with the SQLite command-line utility, you can use a GUI tool to check the contents of the SQLite database. DB Browser for SQLite is a useful multiplatform and free GUI tool that allows us to easily check the database contents of an SQLite database in macOS, Linux and Windows. You can read more information about this tool and download its different versions from http://sqlitebrowser.org. Once you installed the tool, you just need to open the db.sqlite3 file and you can check the database structure and browse the data for the different tables. You can use also the database tools included in your favorite IDE to check the contents for the SQLite database.

The SQLite database engine and the database file name are specified in the gamesapi/settings.py Python file. The following lines show the declaration of the DATABASES dictionary that contains the settings for all the database that Django uses. The nested dictionary maps the database named default with the django.db.backends.sqlite3 database engine and the db.sqlite3 database file located in the BASE_DIR folder (gamesapi):

DATABASES = { 
    'default': { 
        'ENGINE': 'django.db.backends.sqlite3', 
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 
    } 
} 

After we executed the migrations, the SQLite database will have the following tables:

  • auth_group

  • auth_group_permissions

  • auth_permission

  • auth_user

  • auth_user_groups

  • auth_user_groups_permissions

  • django_admin_log

  • django_content_type

  • django_migrations

  • django_session

  • games_game

  • sqlite_sequence

The games_game table persists in the database the Game class we recently created, specifically, the Game model. Django's integrated ORM generated the games_game table based on our Game model. The games_game table has the following rows (also known as fields) with their SQLite types and all of them are not nullable:

  • id: The integer primary key, an autoincrement row

  • created: datetime

  • name: varchar(200)

  • release_date: datetime

  • game_category: varchar(200)

  • played: bool

The following lines show the SQL creation script that Django generated when we executed the migrations:

CREATE TABLE "games_game" ( 
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 
    "created" datetime NOT NULL, 
    "name" varchar(200) NOT NULL, 
    "release_date" datetime NOT NULL, 
    "game_category" varchar(200) NOT NULL, 
    "played" bool NOT NULL 
) 

Django generated additional tables that it requires to support the Web framework and the authentication features that we will use later.

 

Managing serialization and deserialization


Our RESTful Web API has to be able to serialize and deserialize the game instances into JSON representations. With Django REST Framework, we just need to create a serializer class for the game instances to manage serialization to JSON and deserialization from JSON.

Django REST Framework uses a two-phase process for serialization. The serializers are mediators between the model instances and Python primitives. Parser and renderers handle as mediators between Python primitives and HTTP requests and responses. We will configure our mediator between the Game model instances and Python primitives by creating a subclass of the rest_framework.serializers.Serializer class to declare the fields and the necessary methods to manage serialization and deserialization. We will repeat some of the information about the fields that we have included in the Game model so that we understand all the things that we can configure in a subclass of the Serializer class. However, we will work with shortcuts that will reduce boilerplate code later in the next examples. We will write less code in the next examples by using the ModelSerializer class.

Now, go to the gamesapi/games folder folder and create a new Python code file named serializers.py. The following lines show the code that declares the new GameSerializer class. The code file for the sample is included in the restful_python_chapter_01_01 folder.

from rest_framework import serializers 
from games.models import Game 
 
 
class GameSerializer(serializers.Serializer): 
    pk = serializers.IntegerField(read_only=True) 
    name = serializers.CharField(max_length=200) 
    release_date = serializers.DateTimeField() 
    game_category = serializers.CharField(max_length=200) 
    played = serializers.BooleanField(required=False) 
 
    def create(self, validated_data): 
        return Game.objects.create(**validated_data) 
 
    def update(self, instance, validated_data): 
        instance.name = validated_data.get('name', instance.name) 
        instance.release_date = validated_data.get('release_date', instance.release_date) 
        instance.game_category = validated_data.get('game_category', instance.game_category) 
        instance.played = validated_data.get('played', instance.played) 
        instance.save() 
        return instance 

The GameSerializer class declares the attributes that represent the fields that we want to be serialized. Notice that they have omitted the created attribute that was present in the Game model. When there is a call to the inherited save method for this class, the overridden create and update methods define how to create or modify an instance. In fact, these methods must be implemented in our class because they just raise a NotImplementedError exception in their base declaration.

The create method receives the validated data in the validated_data argument. The code creates and returns a new Game instance based on the received validated data.

The update method receives an existing Game instance that is being updated and the new validated data in the instance and validated_data arguments. The code updates the values for the attributes of the instance with the updated attribute values retrieved from the validated data, calls the save method for the updated Game instance and returns the updated and saved instance.

We can launch our default Python interactive shell and make all the Django project modules available before it starts. This way, we can check that the serializer works as expected. In addition, it will help us understanding how serialization works in Django. Run the following command to launch the interactive shell. Make sure you are within the gamesapi folder in the Terminal or command prompt:

python manage.py shell

You will notice that a line that says (InteractiveConsole) is displayed after the usual lines that introduce your default Python interactive shell. Enter the following code in the Python interactive shell to import all the things we will need to test the Game model and its serializer. The code file for the sample is included in the restful_python_chapter_01_01 folder, in the serializers_test_01.py file:

from datetime import datetime 
from django.utils import timezone 
from django.utils.six import BytesIO 
from rest_framework.renderers import JSONRenderer 
from rest_framework.parsers import JSONParser 
from games.models import Game 
from games.serializers import GameSerializer 

Enter the following code to create two instances of the Game model and save them. The code file for the sample is included in the restful_python_chapter_01_01 folder, in the serializers_test_01.py file:

gamedatetime = timezone.make_aware(datetime.now(), timezone.get_current_timezone()) 
game1 = Game(name='Smurfs Jungle', release_date=gamedatetime, game_category='2D mobile arcade', played=False) 
game1.save() 
game2 = Game(name='Angry Birds RPG', release_date=gamedatetime, game_category='3D RPG', played=False) 
game2.save() 

After we execute the preceding code, we can check the SQLite database with the previously introduce command-line or GUI tool to check the contents of the games_game table. We will notice the table has two rows and the columns have the values we have provided to the different attributes of the Game instances.

Enter the following commands in the interactive shell to check the values for the primary keys or identifiers for the saved Game instances and the value of the created attribute includes the date and time in which we saved the instance to the database. The code file for the sample is included in the restful_python_chapter_01_01 folder, in the serializers_test_01.py file:

print(game1.pk) 
print(game1.name) 
print(game1.created) 
print(game2.pk) 
print(game2.name) 
print(game2.created) 

Now, let's write the following code to serialize the first game instance (game1). The code file for the sample is included in the restful_python_chapter_01_01 folder, in the serializers_test_01.py file:

game_serializer1 = GameSerializer(game1) 
print(game_serializer1.data) 

The following line shows the generated dictionary, specifically, a rest_framework.utils.serializer_helpers.ReturnDict instance:

{'release_date': '2016-05-18T03:02:00.776594Z', 'game_category': '2D mobile arcade', 'played': False, 'pk': 2, 'name': 'Smurfs Jungle'} 

Now, let's serialize the second game instance (game2). The code file for the sample is included in the restful_python_chapter_01_01 folder, in the serializers_test_01.py file:

game_serializer2 = GameSerializer(game2) 
print(game_serializer2.data) 

The following line shows the generated dictionary:

{'release_date': '2016-05-18T03:02:00.776594Z', 'game_category': '3D RPG', 'played': False, 'pk': 3, 'name': 'Angry Birds RPG'} 

We can easily render the dictionaries hold in the data attribute into JSON with the help of the rest_framework.renderers.JSONRenderer class. The following lines create an instance of this class and then calls the render method to render the dictionaries hold in the data attribute into JSON. The code file for the sample is included in the restful_python_chapter_01_01 folder, in the serializers_test_01.py file:

renderer = JSONRenderer() 
rendered_game1 = renderer.render(game_serializer1.data) 
rendered_game2 = renderer.render(game_serializer2.data) 
print(rendered_game1) 
print(rendered_game2) 

The following lines show the output generated from the two calls to the render method:

b'{"pk":2,"name":"Smurfs Jungle","release_date":"2016-05-
    18T03:02:00.776594Z","game_category":"2D mobile arcade","played":false}'
b'{"pk":3,"name":"Angry Birds RPG","release_date":"2016-05-
18T03:02:00.776594Z","game_category":"3D RPG","played":false}'

Now, we will work in the opposite direction: from serialized data to the population of a Game instance. The following lines generate a new Game instance from a JSON string (serialized data), that is, they will deserialize. The code file for the sample is included in the restful_python_chapter_01_01 folder, in the serializers_test_01.py file:

json_string_for_new_game = '{"name":"Tomb Raider Extreme Edition","release_date":"2016-05-18T03:02:00.776594Z","game_category":"3D RPG","played":false}' 
json_bytes_for_new_game = bytes(json_string_for_new_game , encoding="UTF-8") 
stream_for_new_game = BytesIO(json_bytes_for_new_game) 
parser = JSONParser() 
parsed_new_game = parser.parse(stream_for_new_game) 
print(parsed_new_game) 

The first line creates a new string with the JSON that defines a new game (json_string_for_new_game). Then, the code converts the string to bytes and saves the results of the conversion in the json_bytes_for_new_game variable. The django.utils.six.BytesIO class provides a buffered I/O implementation using an in-memory bytes buffer. The code uses this class to create a stream from the previously generated JSON bytes with the serialized data, json_bytes_for_new_game, and saves the generated instance in the stream_for_new_game variable.

We can easily deserialize and parse a stream into the Python models with the help of the rest_framework.parsers.JSONParser class. The next line creates an instance of this class and then calls the parse method with stream_for_new_game as an argument, parses the stream into Python native datatypes and saves the results in the parsed_new_game variable.

After executing the preceding lines, parsed_new_game holds a Python dictionary, parsed from the stream. The following lines show the output generated after executing the preceding code snippet:

{'release_date': '2016-05-18T03:02:00.776594Z', 'played': False,
    'game_category': '3D RPG', 'name': 'Tomb Raider Extreme Edition'}

The following lines use the GameSerializer class to generate a fully populated Game instance named new_game from the Python dictionary, parsed from the stream. The code file for the sample is included in the restful_python_chapter_01_01 folder, in the serializers_test_01.py file.

new_game_serializer = GameSerializer(data=parsed_new_game) 
if new_game_serializer.is_valid(): 
    new_game = new_game_serializer.save() 
    print(new_game.name) 

First, the code creates an instance of the GameSerializer class with the Python dictionary that we previously parsed from the stream (parsed_new_game) passed as the data keyword argument. Then, the code calls the is_valid method to determine whether the data is valid. Notice that we must always call is_valid before we attempt to access the serialized data representation when we pass a data keyword argument in the creation of a serializer.

If the method returns true, we can access the serialized representation in the data attribute, and therefore, the code calls the save method that inserts the corresponding row in the database and returns a fully populated Game instance, saved in the new_game local variable. Then, the code prints one of the attributes from the fully populated Game instance. After executing the preceding code, we fully populated two Game instances: new_game1_instance and new_game2_instance.

Tip

As we can learn from the preceding code, Django REST Framework makes it easy to serialize from objects to JSON and deserialize from JSON to objects, which are core requirements for our RESTful Web API that has to perform CRUD operations.

Enter the following command to leave the shell with the Django project modules that we started to test serialization and deserialization:

quit() 
 

Writing API views


Now, we will create Django views that will use the previously created GameSerializer class to return JSON representations for each HTTP request that our API will handle. Open the games/views.py file. The following lines show the initial code for this file, with just one import statement and a comment that indicates we should create the views.

from django.shortcuts import render 
 
# Create your views here. 

The following lines show the new code that creates a JSONResponse class and declares two functions: game_list and game_detail, in the games/views.py file. We are creating our first version of the API, and we use functions to keep the code as simple as possible. We will work with classes and more complex code in the next examples. The highlighted lines show the expressions that evaluate the value of the request.method attribute to determine the actions to be performed based on the HTTP verb. The code file for the sample is included in the restful_python_chapter_01_01 folder:

from django.http import HttpResponse 
from django.views.decorators.csrf import csrf_exempt 
from rest_framework.renderers import JSONRenderer 
from rest_framework.parsers import JSONParser 
from rest_framework import status 
from games.models import Game 
from games.serializers import GameSerializer 
 
 
class JSONResponse(HttpResponse): 
    def __init__(self, data, **kwargs): 
        content = JSONRenderer().render(data) 
        kwargs['content_type'] = 'application/json' 
        super(JSONResponse, self).__init__(content, **kwargs) 
 
 
@csrf_exempt 
def game_list(request): 
    if request.method == 'GET': 
        games = Game.objects.all() 
        games_serializer = GameSerializer(games, many=True) 
        return JSONResponse(games_serializer.data) 
 
    elif request.method == 'POST': 
        game_data = JSONParser().parse(request) 
        game_serializer = GameSerializer(data=game_data) 
        if game_serializer.is_valid(): 
            game_serializer.save() 
            return JSONResponse(game_serializer.data,
            status=status.HTTP_201_CREATED) 
        return JSONResponse(game_serializer.errors,
        status=status.HTTP_400_BAD_REQUEST) 
 
 
@csrf_exempt 
def game_detail(request, pk): 
    try: 
        game = Game.objects.get(pk=pk) 
    except Game.DoesNotExist: 
        return HttpResponse(status=status.HTTP_404_NOT_FOUND) 
 
    if request.method == 'GET': 
        game_serializer = GameSerializer(game) 
        return JSONResponse(game_serializer.data) 
 
    elif request.method == 'PUT': 
        game_data = JSONParser().parse(request) 
        game_serializer = GameSerializer(game, data=game_data) 
        if game_serializer.is_valid(): 
            game_serializer.save() 
            return JSONResponse(game_serializer.data) 
        return JSONResponse(game_serializer.errors,
        status=status.HTTP_400_BAD_REQUEST) 
 
    elif request.method == 'DELETE': 
        game.delete() 
        return HttpResponse(status=status.HTTP_204_NO_CONTENT) 

The JSONResponse class is a subclass of the django.http.HttpResponse class. The superclass represents an HTTP response with a string as content. The JSONResponse class renders its content into JSON. The class defines just declare the __init__ method that created a rest_framework.renderers.JSONRenderer instance and calls its render method to render the received data into JSON save the returned bytestring in the content local variable. Then, the code adds the 'content_type' key to the response header with 'application/json' as its value. Finally, the code calls the initializer for the base class with the JSON bytestring and the key-value pair added to the header. This way, the class represents a JSON response that we use in the two functions to easily return a JSON response.

The code uses the @csrf_exempt decorator in the two functions to ensure that the view sets a Cross-Site Request Forgery (CSRF) cookie. We do this to make it simple to test this example that doesn't represent a production-ready Web Service. We will add security features to our RESTful API later.

When the Django server receives an HTTP request, Django creates an HttpRequest instance, specifically a django.http.HttpRequest object. This instance contains metadata about the request, including the HTTP verb. The method attribute provides a string representing the HTTP verb or method used in the request.

When Django loads the appropriate view that will process the requests, it passes the HttpRequest instance as the first argument to the view function. The view function has to return an HttpResponse instance, specifically a django.http.HttpResponse instance.

The game_list function lists all the games or creates a new game. The function receives an HttpRequest instance in the request argument. The function is capable of processing two HTTP verbs: GET and POST. The code checks the value of the request.method attribute to determine the code to be executed based on the HTTP verb. If the HTTP verb is GET, the expression request.method == 'GET' will evaluate to True and the code has to list all the games. The code will retrieve all the Game objects from the database, use the GameSerializer to serialize all of them, and return a JSONResponse instance built with the data generated by the GameSerializer. The code creates the GameSerializer instance with the many=True argument to specify that multiple instances have to be serialized and not just one. Under the hoods, Django uses a ListSerializer when the many argument value is set to True.

If the HTTP verb is POST, the code has to create a new game based on the JSON data that is included in the HTTP request. First, the code uses a JSONParser instance and calls its parse method with request as an argument to parse the game data provided as JSON data in the request and saves the results in the game_data local variable. Then, the code creates a GameSerializer instance with the previously retrieved data and calls the is_valid method to determine whether the Game instance is valid or not. If the instance is valid, the code calls the save method to persist the instance in the database and returns a JSONResponse with the saved data in its body and a status equal to status.HTTP_201_CREATED, that is, 201 Created.

Tip

Whenever we have to return a specific status different from the default 200 OK status, it is a good practice to use the module variables defined in the rest_framework.status module and to avoid using hardcoded numeric values.

The game_detail function retrieves, updates or deletes an existing game. The function receives an HttpRequest instance in the request argument and the primary key or identifier for the game to be retrieved, updated or deleted in the pk argument. The function is capable of processing three HTTP verbs: GET, PUT and DELETE. The code checks the value of the request.method attribute to determine the code to be executed based on the HTTP verb. No matter which is the HTTP verb, the function calls the Game.objects.get method with the received pk as the pk argument to retrieve a Game instance from the database based on the specified primary key or identifier, and saves it in the game local variable. In case a game with the specified primary key or identifier doesn't exist in the database, the code returns an HttpResponse with its status equal to status.HTTP_404_NOT_FOUND, that is, 404 Not Found.

If the HTTP verb is GET, the code creates a GameSerializer instance with game as an argument and returns the data for the serialized game in a JSONResponse that will include the default 200 OK status. The code returns the retrieved game serialized as JSON.

If the HTTP verb is PUT, the code has to create a new game based on the JSON data that is included in the HTTP request and use it to replace an existing game. First, the code uses a JSONParser instance and calls its parse method with request as an argument to parse the game data provided as JSON data in the request and saves the results in the game_data local variable. Then, the code creates a GameSerializer instance with the Game instance previously retrieved from the database (game) and the retrieved data that will replace the existing data (game_data). Then, the code calls the is_valid method to determine whether the Game instance is valid or not. If the instance is valid, the code calls the save method to persist the instance with the replaced values in the database and returns a JSONResponse with the saved data in its body and the default 200 OK status. If the parsed data doesn't generate a valid Game instance, the code returns a JSONResponse with a status equal to status.HTTP_400_BAD_REQUEST, that is, 400 Bad Request.

If the HTTP verb is DELETE, the code calls the delete method for the Game instance previously retrieved from the database (game). The call to the delete method erases the underlying row in the games_game table, and therefore, the game won't be available anymore. Then, the code returns a JSONResponse with a status equal to status.HTTP_204_NO_CONTENT that is, 204 No Content.

Now, we have to create a new Python file named urls.py in the games folder, specifically, the games/urls.py file. The following lines show the code for this file that defines the URL patterns that specifies the regular expressions that have to be matched in the request to run a specific function defines in the views.py file. The code file for the sample is included in the restful_python_chapter_01_01 folder:

from django.conf.urls import url 
from games import views 
 
urlpatterns = [ 
    url(r'^games/$', views.game_list), 
    url(r'^games/(?P<pk>[0-9]+)/$', views.game_detail), 
] 

The urlpatterns list makes it possible to route URLs to views. The code calls the django.conf.urls.url function with the regular expression that has to be matched and the view function defined in the views module as arguments to create a RegexURLPattern instance for each entry in the urlpatterns list.

We have to replace the code in the urls.py file in the gamesapi folder, specifically, the gamesapi/urls.py file. The file defines the root URL configurations, and therefore, we must include the URL patterns declared in the previously coded games/urls.py file. The following lines show the new code for the gamesapi/urls.py file. The code file for the sample is included in the restful_python_chapter_01_01 folder:

from django.conf.urls import url, include 
 
urlpatterns = [ 
    url(r'^', include('games.urls')), 
] 

Now, we can launch Django's development server to compose and send HTTP requests to our unsecure Web API (we will definitely add security later). Execute the following command:

python manage.py runserver

The following lines show the output after we execute the preceding command. The development server is listening at port 8000 .

Performing system checks...
System check identified no issues (0 silenced).
May 20, 2016 - 04:22:38
Django version 1.9.6, using settings 'gamesapi.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

With the preceding command, we will start Django development server and we will only be able to access it in our development computer. The preceding command starts the development server in the default IP address, that is, 127.0.0.1 (localhost). It is not possible to access this IP address from other computers or devices connected on our LAN. Thus, if we want to make HTTP requests to our API from other computers or devices connected to our LAN, we should use the development computer IP address, 0.0.0.0 (for IPv4 configurations), or :: (for IPv6 configurations) as the desired IP address for our development server.

If we specify 0.0.0.0 as the desired IP address for IPv4 configurations, the development server will listen on every interface on port 8000. When we specify :: for IPv6 configurations, it will have the same effect. In addition, it is necessary to open the default port 8000 in our firewalls (software and/or hardware) and configure port-forwarding to the computer that is running the development server. The following command launches Django's development server in an IPv4 configuration and allows requests to be made from other computers and devices connected to our LAN:

python manage.py runserver 0.0.0.0:8000

Tip

If you decide to compose and send HTTP requests from other computers or devices connected to the LAN, remember that you have to use the development computer's assigned IP address instead of localhost. For example, if the computer's assigned IPv4 IP address is 192.168.1.106, instead of localhost:8000, you should use 192.168.1.106:8000. Of course, you can also use the host name instead of the IP address. The previously explained configurations are very important because mobile devices might be the consumers of our RESTful APIs and we will always want to test the apps that make use of our APIs in our development environments.

 

Making HTTP requests to the API


The Django development server is running on localhost (127.0.0.1), listening on port 8000, and waiting for our HTTP requests. Now, we will compose and send HTTP requests locally in our development computer or from other computer or devices connected to our LAN. We will use the following different kind of tools to compose and send HTTP requests throughout our book.

  • Command-line tools

  • GUI tools

  • Python code

  • JavaScript code

Tip

Notice that you can use any other application that allows you to compose and send HTTP requests. There are many apps that run on tablets and smartphones that allow you to accomplish this task. However, we will focus our attention on the most useful tools when building RESTful Web APIs.

Working with command-line tools - curl and httpie

We will start with command-line tools. One of the key advantages of command-line tools is that we can easily run again the HTTP requests after we built them for the first time, and we don't need to use the mouse or tap the screen to run requests. We can also easily build a script with batch requests and run them. As happens with any command-line tool, it can take more time to perform the first requests compared with GUI tools, but it becomes easier once we performed many requests and we can easily reuse the commands we have written in the past to compose new requests.

Curl, also known as cURL, is a very popular open source command-line tool and library that allow us to easily transfer data. We can use the curl command-line tool to easily compose and send HTTP requests and check their responses.

Tip

If you are working on either macOS or Linux, you can open a Terminal and start using curl from the command line. If you are working on any Windows version, you can easily install curl from the Cygwin package installation option, and execute it from the Cygwin terminal. You can read more about the curl utility at http://curl.haxx.se. You can read more about the Cygwin terminal and its installation procedure at http://cygwin.com/install.html.

Open a Cygwin terminal in Windows or a terminal in macOS or Linux, and run the following command. It is very important that you enter the ending slash (/) because /games won't match any of the patterns specified in urlpatterns in the games/urls.py file. We are using the default configuration for Django that doesn't redirect URLs that don't match any of the patterns to the same URLs with a slash appended. Thus, we must enter /games/, including the ending slash (/):

curl -X GET :8000/games/

The preceding command will compose and send the following HTTP request-GET http://localhost:8000/games/. The request is the simplest case in our RESTful API because it will match and run the views.game_list function, that is, the game_list function declared within the games/views.py file. The function just receives request as a parameter because the URL pattern doesn't include any parameters. As the HTTP verb for the request is GET, the request.method property is equal to 'GET', and therefore, the function will execute the code that retrieves all the Game objects and generates a JSON response with all of these Game objects serialized.

The following lines show an example response for the HTTP request, with three Game objects in the JSON response:

[{"pk":3,"name":"Angry Birds RPG","release_date":"2016-05-18T03:02:00.776594Z","game_category":"3D RPG","played":false},{"pk":2,"name":"Smurfs Jungle","release_date":"2016-05-18T03:02:00.776594Z","game_category":"2D mobile arcade","played":false},{"pk":11,"name":"Tomb Raider Extreme Edition","release_date":"2016-05-18T03:02:00.776594Z","game_category":"3D RPG","played":false}]

As we might notice from the previous response, the curl utility displays the JSON response in a single line, and therefore, it is a bit difficult to read it. In this case, we know that the Content-Type of the response is application/json. However, in case we want to have more details about the response, we can use the -i option to request curl to print the HTTP response headers. We can combine the -i and -X options by using -iX.

Go back to the Cygwin terminal in Windows or the Terminal in macOS or Linux, and run the following command:

curl -iX GET :8000/games/

The following lines show an example response for the HTTP request. The first lines show the HTTP response headers, including the status (200 OK) and the Content-type (application/json). After the HTTP response headers, we can see the details for the three Game objects in the JSON response:

HTTP/1.0 200 OK
Date: Tue, 24 May 2016 18:04:40 GMT
Server: WSGIServer/0.2 CPython/3.5.1
Content-Type: application/json
X-Frame-Options: SAMEORIGIN
[{"pk":3,"name":"Angry Birds RPG","release_date":"2016-05-18T03:02:00.776594Z","game_category":"3D RPG","played":false},{"pk":2,"name":"Smurfs Jungle","release_date":"2016-05-18T03:02:00.776594Z","game_category":"2D mobile arcade","played":false},{"pk":11,"name":"Tomb Raider Extreme Edition","release_date":"2016-05-18T03:02:00.776594Z","game_category":"3D RPG","played":false}]

After we run the two requests, we will see the following lines in the window that is running the Django development server. The output indicates that the server received two HTTP requests with the GET verb and /games/ as the URI. The server processed both HTTP requests, returned status code 200 and the response length was equal to 379 characters. The response length can be different because the value for the primary key assigned to each game will have an incidence in the response length. The first number after HTTP/1.1." indicates the returned status code (200) and the second number the response length (379).

[25/May/2016 04:35:09] "GET /games/ HTTP/1.1" 200 379
[25/May/2016 04:35:10] "GET /games/ HTTP/1.1" 200 379

The following image shows two terminal windows side-by-side on macOS. The Terminal window at the left-hand side is running the Django development server and displays the received and processed HTTP requests. The Terminal window at the right-hand side is running curl commands to generate the HTTP requests.

It is a good idea to use a similar configuration to check the output while we compose and send the HTTP requests. Notice that the JSON outputs are a bit difficult to read because they don't use syntax highlighting:

Now, we will install HTTPie, a command-line HTTP client written in Python that makes it easy to send HTTP requests and uses a syntax that is easier than curl (also known as cURL). One of the great advantages of HTTPie is that it displays colorized output and uses multiple lines to display the response details. Thus, HTTPie makes it easier to understand the responses than the curl utility. We just need to activate the virtual environment and then run the following command in the terminal or command prompt to install the HTTPie package:

pip install --upgrade httpie

The last lines for the output will indicate that the django package has been successfully installed.

Collecting httpie
  Downloading httpie-0.9.3-py2.py3-none-any.whl (66kB)
Collecting requests>=2.3.0 (from httpie)
  Using cached requests-2.10.0-py2.py3-none-any.whl
Collecting Pygments>=1.5 (from httpie)
  Using cached Pygments-2.1.3-py2.py3-none-any.whl
Installing collected packages: requests, Pygments, httpie
Successfully installed Pygments-2.1.3 httpie-0.9.3 requests-2.10.0

Tip

In case you don't remember how to activate the virtual environment that we created for this example, read the following section in this chapter-Setting up the virtual environment with Django REST framework.

Now, we can use an http command to easily compose and send HTTP requests to localhost:8000 and test the RESTful API built with Django REST framework. HTTPie supports curl-like shorthands for localhost, and therefore, we can use :8000 as a shorthand that expands to http://localhost:8000. Run the following command and remember to enter the ending slash (/):

http :8000/games/

The preceding command will compose and send the following HTTP request: GET http://localhost:8000/games/. The request is the same one we have previously composed with the curl command. However, in this case, the HTTPie utility will display a colorized output and it will use multiple lines to display the JSON response. The preceding command is equivalent to the following command that specifies the GET method after http:

http GET :8000/games/

The following lines show an example response for the HTTP request, with the headers and the three Game objects in the JSON response. It is indeed easier to understand the response compared with the results generated when we composed the HTTP request with curl. HTTPie automatically formats the JSON data received as a response and applies syntax highlighting, specifically, both colors and formatting:

HTTP/1.0 200 OK
Content-Type: application/json
Date: Thu, 26 May 2016 21:33:17 GMT
Server: WSGIServer/0.2 CPython/3.5.1
X-Frame-Options: SAMEORIGIN
[
    {
        "game_category": "3D RPG",
        "name": "Angry Birds RPG",
        "pk": 3,
        "played": false,
        "release_date": "2016-05-18T03:02:00.776594Z"
    },
    {
        "game_category": "2D mobile arcade",
        "name": "Smurfs Jungle",
        "pk": 2,
        "played": false,
        "release_date": "2016-05-18T03:02:00.776594Z"
    },
    {
        "game_category": "3D RPG",
        "name": "Tomb Raider Extreme Edition",
        "pk": 11,
        "played": false,
        "release_date": "2016-05-18T03:02:00.776594Z"
    }
]

Tip

We can achieve the same results by combining the output generated with the curl command with other utilities. However, HTTPie provides us exactly what we need to work with RESTful APIs. We will use HTTPie to compose and send HTTP request, but we will always provide the equivalent curl command.

The following image shows two Terminal windows side-by-side on macOS. The terminal window at the left-hand side is running the Django development server and displays the received and processed HTTP requests. The Terminal window at the right-hand side is running HTTPie commands to generate the HTTP requests. Notice that the JSON output is easier to read compared to the output generated by the curl command:

We can execute HTTPie with the -b option in case we don't want to include the header in the response. For example, the following line performs the same HTTP request but doesn't display the header in the response output, and therefore, the output will just display the JSON response:

http -b :8000/games/

Now, we will select one of the games from the preceding list and we will compose an HTTP request to retrieve just the chosen game. For example, in the previous list, the first game has a pk value equal to 3. Run the following command to retrieve this game. Use the pk value you have retrieved in the previous command for the first game, as the pk number might be different:

http :8000/games/3/

The following is the equivalent curl command:

curl -iX GET :8000/games/3/

The previous commands will compose and send the following HTTP request: GET http://localhost:8000/games/3/. The request has a number after /games/, and therefore, it will match '^games/(?P<pk>[0-9]+)/$' and run the views.game_detail function, that is, the game_detail function declared within the games/views.py file. The function receives request and pk as parameters because the URL pattern passes the number specified after /games/ in the pk parameter. As the HTTP verb for the request is GET, the request.method property is equal to 'GET', and therefore, the function will execute the code that retrieves the Game object whose primary key matches the pk value received as an argument and, if found, generates a JSON response with this Game object serialized. The following lines show an example response for the HTTP request, with the Game object that matches the pk value in the JSON response:

HTTP/1.0 200 OK
Content-Type: application/json
Date: Fri, 27 May 2016 02:28:30 GMT
Server: WSGIServer/0.2 CPython/3.5.1
X-Frame-Options: SAMEORIGIN
{
    "game_category": "3D RPG",
    "name": "Angry Birds RPG",
    "pk": 3,
    "played": false,
    "release_date": "2016-05-18T03:02:00.776594Z"
}

Now, we will compose and send an HTTP request to retrieve a game that doesn't exist. For example, in the preceding list, there is no game with a pk value equal to 99999. Run the following command to try to retrieve this game. Make sure you use a pk value that doesn't exist. We must make sure that the utilities display the headers as part of the response because the response won't have a body:

http :8000/games/99999/

The following is the equivalent curl command:

curl -iX GET :8000/games/99999/

The preceding commands will compose and send the following HTTP request: GET http://localhost:8000/games/99999/. The request is the same than the previous one we have analyzed, with a different number for the pk parameter. The server will run the views.game_detail function, that is, the game_detail function declared within the games/views.py file. The function will execute the code that retrieves the Game object whose primary key matches the pk value received as an argument and a Game.DoesNotExist exception will be thrown and captured because there is no game with the specified pk value. Thus, the code will return an HTTP 404 Not Found status code. The following lines show an example header response for the HTTP request:

HTTP/1.0 404 Not Found
Content-Type: text/html; charset=utf-8
Date: Fri, 27 May 2016 02:20:41 GMT
Server: WSGIServer/0.2 CPython/3.5.1
X-Frame-Options: SAMEORIGIN

We will compose and send an HTTP request to create a new game.

http POST :8000/games/ name='PvZ 3' game_category='2D mobile arcade' played=false release_date='2016-05-18T03:02:00.776594Z'

The following is the equivalent curl command. It is very important to use the -H "Content-Type: application/json" option to indicate curl to send the data specified after the -d option as application/json instead of the default application/x-www-form-urlencoded:

curl -iX POST -H "Content-Type: application/json" -d '{"name":"PvZ 3", "game_category":"2D mobile arcade", "played": "false", "release_date": "2016-05-18T03:02:00.776594Z"}' :8000/games/

The previous commands will compose and send the following HTTP request: POST http://localhost:8000/games/ with the following JSON key-value pairs:

{  
    "name": "PvZ 3",  
    "game_category": "2D mobile arcade",  
    "played": false,  
    "release_date": "2016-05-18T03:02:00.776594Z" 
} 

The request specifies /games/, and therefore, it will match '^games/$' and run the views.game_list function, that is, the game_detail function declared within the games/views.py file. The function just receives request as a parameter because the URL pattern doesn't include any parameters. As the HTTP verb for the request is POST, the request.method property is equal to 'POST', and therefore, the function will execute the code that parses the JSON data received in the request, creates a new Game and, if the data is valid, it saves the new Game. If the new Game was successfully persisted in the database, the function returns an HTTP 201 Created status code and the recently persisted Game serialized serialized to JSON in the response body. The following lines show an example response for the HTTP request, with the new Game object in the JSON response:

HTTP/1.0 201 Created
Content-Type: application/json
Date: Fri, 27 May 2016 05:12:39 GMT
Server: WSGIServer/0.2 CPython/3.5.1
X-Frame-Options: SAMEORIGIN
{
    "game_category": "2D mobile arcade",
    "name": "PvZ 3",
    "pk": 15,
    "played": false,
    "release_date": "2016-05-18T03:02:00.776594Z"
}

Now, we will compose and send an HTTP request to update an existing game, specifically, the previously added game. We have to check the value assigned to pk in the previous response and replace 15 in the command with the returned value. For example, in case the value for pk was 5, you should use :8000/games/5/ instead of :8000/games/15/.

http PUT :8000/games/15/ name='PvZ 3' game_category='2D mobile arcade' played=true release_date='2016-05-20T03:02:00.776594Z'

The following is the equivalent curl command. As happened with the previous curl example, it is very important to use the -H "Content-Type: application/json" option to indicate curl to send the data specified after the -d option as application/json instead of the default application/x-www-form-urlencoded:

curl -iX PUT -H "Content-Type: application/json" -d '{"name":"PvZ 3", "game_category":"2D mobile arcade", "played": "true", "release_date": "2016-05-20T03:02:00.776594Z"}' :8000/games/15/

The previous commands will compose and send the following HTTP request: PUT http://localhost:8000/games/15/ with the following JSON key-value pairs:

{  
    "name": "PvZ 3",  
    "game_category": "2D mobile arcade",  
    "played": true,  
    "release_date": "2016-05-20T03:02:00.776594Z" 
} 

The request has a number after /games/, and therefore, it will match '^games/(?P<pk>[0-9]+)/$' and run the views.game_detail function, that is, the game_detail function declared within the games/views.py file. The function receives request and pk as parameters because the URL pattern passes the number specified after /games/ in the pk parameter. As the HTTP verb for the request is PUT, the request.method property is equal to 'PUT', and therefore, the function will execute the code that parses the JSON data received in the request, creates a Game instance from this data and updates the existing game in the database. If the game was successfully updated in the database, the function returns an HTTP 200 OK status code and the recently updated Game serialized serialized to JSON in the response body. The following lines show an example response for the HTTP request, with the updated Game object in the JSON response:

HTTP/1.0 200 OK
Content-Type: application/json
Date: Sat, 28 May 2016 00:49:05 GMT
Server: WSGIServer/0.2 CPython/3.5.1
X-Frame-Options: SAMEORIGIN
{
    "game_category": "2D mobile arcade",
    "name": "PvZ 3",
    "pk": 15,
    "played": true,
    "release_date": "2016-05-20T03:02:00.776594Z"
}

In order to successfully process a PUT HTTP request that updates an existing game, we must provide values for all the required fields. We will compose and send an HTTP request to try update an existing game, and we will fail to do so because we will just provide a value for the name. As happened in the previous request, we will use the value assigned to pk in the last game we added:

http PUT :8000/games/15/ name='PvZ 4'

The following is the equivalent curl command:

curl -iX PUT -H "Content-Type: application/json" -d '{"name":"PvZ 4"}'
    :8000/games/15/

The previous commands will compose and send the following HTTP request: PUT http://localhost:8000/games/15/ with the following JSON key-value pair:

{  
    "name": "PvZ 4",  
} 

The request will execute the same code we explained for the previous request. Because we didn't provide all the required values for a Game instance, the game_serializer.is_valid() method will return False and the function will return an HTTP 400 Bad Request status code and the details generated in the game_serializer.errors attribute serialized to JSON in the response body. The following lines show an example response for the HTTP request, with the required fields that our request didn't include values in the JSON response:

HTTP/1.0 400 Bad Request
Content-Type: application/json
Date: Sat, 28 May 2016 02:53:08 GMT
Server: WSGIServer/0.2 CPython/3.5.1
X-Frame-Options: SAMEORIGIN
{
    "game_category": [
        "This field is required."
    ],
    "release_date": [
        "This field is required."
    ]
}

Tip

When we want our API to be able to update a single field for an existing resource, in this case, an existing game, we should provide an implementation for the PATCH method. The PUT method is meant to replace an entire resource and the PATCH method is meant to apply a delta to an existing resource. We can write code in the handler for the PUT method apply a delta to an existing resource, but it is a better practice to use the PATCH method for this specific task. We will work with the PATCH method later.

Now, we will compose and send an HTTP request to delete an existing game, specifically, the last game we added. As happened in our last HTTP requests, we have to check the value assigned to pk in the previous response and replace 12 in the command with the returned value:

http DELETE :8000/games/15/

The following is the equivalent curl command:

curl -iX DELETE :8000/games/15/

The preceding commands will compose and send the following HTTP request: DELETE http://localhost:8000/games/15/. The request has a number after /games/, and therefore, it will match '^games/(?P<pk>[0-9]+)/$' and run the views.game_detail function, that is, the game_detail function declared within the games/views.py file. The function receives request and pk as parameters because the URL pattern passes the number specified after /games/ in the pk parameter. As the HTTP verb for the request is DELETE, the request.method property is equal to 'DELETE', and therefore, the function will execute the code that parses the JSON data received in the request, creates a Game instance from this data and deletes the existing game in the database. If the game was successfully deleted in the database, the function returns an HTTP 204 No Content status code. The following lines show an example response for the HTTP request after successfully deleting an existing game:

HTTP/1.0 204 No Content
Date: Sat, 28 May 2016 04:08:58 GMT
Server: WSGIServer/0.2 CPython/3.5.1
Content-Length: 0
X-Frame-Options: SAMEORIGIN
Content-Type: text/html; charset=utf-8

Working with GUI tools - Postman and others

So far, we have been working with two terminal-based or command-line tools to compose and send HTTP requests to our Django development server-cURL and HTTPie. Now, we will work with GUI (Graphical User Interface) tools.

Postman is a very popular API testing suite GUI tool that allows us to easily compose and send HTTP requests, among other features. Postman is available as a Chrome App and as a Mac App. We can execute it in Windows, Linux and macOS as a Chrome App, that is, an application running on top of Google Chrome. In case we work with macOS, we can use the Mac App instead of the Chrome App. You can download the versions of the Postman App from the following URL-https://www.getpostman.com.

Tip

You can download and install Postman for free to compose and send HTTP requests to our RESTful APIs. You just need to sign up to Postman and we won't be using any of the paid features provided by Postman cloud in our examples. All the instructions work with Postman 4.2.2 or greater.

Now, we will use the Builder tab in Postman to easily compose and send HTTP requests to localhost:8000 and test the RESTful API with this GUI tool. Postman doesn't support curl-like shorthands for localhost, and therefore, we cannot use the same shorthands we have been using when composing requests with HTTPie.

Select GET in the dropdown menu at the left-hand side of the Enter request URL textbox, and enter localhost:8000/games/ in this textbox at the right-hand side of the dropdown. Then, click Send and Postman will display the Status (200 OK), the time it took for the request to be processed and the response body with all the games formatted as JSON with syntax highlighting (Pretty view).

The following screenshot shows the JSON response body in Postman for the HTTP GET request:

Click on Headers at the right-hand side of Body and Cookies to read the response headers. The following screenshot shows the layout for the response headers that Postman displays for the preceding response. Notice that Postman displays the Status at the right-hand side of the response and doesn't include it as the first line of the Headers, as happened when we worked with both the cURL and HTTPie utilities:

Now, we will use the Builder tab in Postman to compose and send an HTTP request to create a new game, specifically, a POST request. Follow the next steps:

  1. Select POST in the drop-down menu at the left-hand side of the Enter request URL textbox, and enter localhost:8000/games/ in this textbox at the right-hand side of the dropdown.

  2. Click Body at the right-hand side of Authorization and Headers, within the panel that composes the request.

  3. Activate the raw radio button and select JSON (application/json) in the dropdown at the right-hand side of the binary radio button. Postman will automatically add a Content-type as application/json header, and therefore, you will notice the Headers tab will be renamed to Headers (1), indicating us that there is one key-value pair specified for the request headers.

  4. Enter the following lines in the textbox below the radio buttons, within the Body tab:

{ 
    "name": "Batman vs Superman",  
    "game_category": "3D RPG",  
    "played": false,  
    "release_date": "2016-05-18T03:02:00.776594Z" 
} 

The following screenshot shows the request body in Postman:

We followed the necessary steps to create an HTTP POST request with a JSON body that specifies the necessary key-value pairs to create a new game. Click on Send and Postman will display the Status (201 Created), the time it took for the request to be processed and the response body with the recently added game formatted as JSON with syntax highlighting (Pretty view). The following screenshot shows the JSON response body in Postman for the HTTP POST request.

Tip

If we want to compose and send an HTTP PUT request with Postman, it is necessary to follow the previously explained steps to provide JSON data within the request body.

One of the nice features included in Postman is that we can easily review and again run the HTTP requests we have made by browsing the saved History shown at the left-hand side of the Postman window. The History pane displays a list with the HTTP verb followed by the URL for each HTTP request we have composed and sent. We just need to click on the desired HTTP request and click Send to run it again. The following screenshot shows the many HTTP requests in the History pane and the first one selected to send it again.

JetBrains PyCharm is a very popular multiplatform Python IDE (short for Integrated Development Environment) available on macOS, Linux and Windows. Its paid Professional version includes a REST Client that allows us to test RESTful Web services. In case we work with this version of the IDE, we can compose and send HTTP requests without leaving the IDE. You don't need a JetBrains PyCharm Professional version license to run the examples included in this book. However, as the IDE is very popular, we will learn the necessary steps to compose and send an HTTP request for our API using the REST Client included in this IDE.

Now, we will use the REST Client included in PyCharm professional to compose and send an HTTP request to create a new game, specifically, a POST request. Follow the next steps:

  1. Select Tools | Test RESTful Web Service in the main menu to display the REST Client panel.

  2. Select POST in the HTTP method dropdown menu in the REST Client pane.

  3. Enter localhost:8000 in the Host/port textbox, at the right-hand side of the dropdown.

  4. Enter /games/ in the Path textbox, at the right-hand side of the Host/port textbox.

  5. Make sure that the Request tab is activated and click on the add (+) button at the bottom of the Headers list. The IDE will display a textbox for the name and a dropdown for the value. Enter Content-Type in Name, enter application/json in Value and press Enter.

  6. Activate the Text: radio button in Request Body and click the ... button, on the right-hand side of the Text textbox, to specify the text to send. Enter the following lines in textbox included in the Specify the text to send dialog box and then click on OK.

{ 
    "name": "Teenage Mutant Ninja Turtles",  
    "game_category": "3D RPG",  
    "played": false,  
    "release_date": "2016-05-18T03:02:00.776594Z" 
} 

The following screenshot shows the request built in PyCharm Professional REST Client:

We followed the necessary steps to create an HTTP POST request with a JSON body that specifies the necessary key-value pairs to create a new game. Click on the submit request button, that is, the first button with the play icon at the upper-left corner of the REST Client pane. The REST client will compose and send the HTTP POST request, will activate the Response tab, and display the response code 201 (Created), the time it took for the request to be processed, and the content length at the bottom of the pane.

By default, the REST client will automatically apply JSON syntax highlighting to the response. However, sometimes, the JSON content is displayed without line breaks and it is necessary to click on the reformat response button, that is, the first button in the Response tab. The REST client displays the response headers in another tab, and therefore, it just displays the response body in the Response tab. The following screenshot shows the JSON response body in the REST client for the HTTP POST request:

Tip

If we want to compose and send an HTTP PUT request with the REST Client included in PyCharm Professional, it is necessary to follow the previously explained steps to provide JSON data within the request body.

In case you don't work with PyCharm Professional, run any of the following commands to compose and send the HTTP POST request to create the new game:

http POST :8000/games/ name='Teenage Mutant Ninja Turtles' game_category='3D RPG' played=false release_date='2016-05-18T03:02:00.776594Z'

The following is the equivalent curl command:

curl -iX POST -H "Content-Type: application/json" -d '{"name": "Teenage
Mutant Ninja Turtles", "game_category": "3D RPG", "played": "false",
"release_date": "2016-05-18T03:02:00.776594Z"}' :8000/games/

Telerik Fiddler is a popular tool for Windows developers. Telerik Fiddler is a free Web debugging proxy with a GUI but it only runs on Windows. Its main Web page promotes it as a multi-platform tool, but at the time this book was published, the macOS and Linux versions were completely unstable and their development abandoned. We can use Telerik Fiddler in Windows to compose and send HTTP requests, among other features. You can download Fiddler for Windows from the following URL-https://www.telerik.com/download/fiddler.

Stoplight is a popular powerful API modeling tool that allows us to easily test our APIs. Its HTTP request maker allows us to compose and send requests and generate the necessary code to make them in different programming languages, such as JavaScript, Swift, C#, PHP, Node, and Go, among others. You can sign up to work with Stoplight at the following URL-http://stoplight.io.

We can also use apps that can compose and send HTTP requests from mobile devices to work with the RESTful API. For example, we can work with the iCurlHTTP App on iOS devices such as iPad and iPhone-https://itunes.apple.com/us/app/icurlhttp/id611943891?mt=8. In Android devices, we can work with the HTTP Request App-https://play.google.com/store/apps/details?id=air.http.request&hl=en.

The following screenshot shows the results of composing and sending the following HTTP request with the iCurlHTTP App: GET http://192.168.1.106:8000/games/. Remember that you have to perform the previously explained configurations in your LAN and router to be able to access the Django development server from other devices connected to your LAN. In this case, the IP assigned to the computer running the Django Web server is 192.168.1.106, and therefore, you must replace this IP with the IP assigned to your development computer.

At the time this book was published, the mobile apps that allow you to compose and send HTTP requests do not provide all the features you can find in Postman or command-line utilities.

 

Test your knowledge


  1. If we want to create a simple Player model that we will use to represent and persist players in Django REST framework, we can create:

    1.  A Player class as a subclass of the djangorestframework.models.Model class.

    2.  A Player class as a subclass of the django.db.models.Model class.

    3.  A Player function in the restframeworkmodels.py file.

  2. In the Django REST Framework, serializers are:

    1. Mediators between the model instances and Python primitives.

    2. Mediators between the view functions and Python primitives.

    3. Mediators between the URLs and view functions.

  3. In the Django REST Framework, parsers and renderers:

    1. Handle as mediators between model instances and Python primitives.

    2. Reset the board.

    3. Handle as mediators between Python primitives and HTTP requests and responses.

  4. The urlpatterns list declared in the urls.py file makes it possible to:

    1.  Route URLs to views.

    2.  Route URLs to models.

    3.  Route URLs to Python primitives.

  5. HTTPie is a:

    1. Command-line HTTP server written in Python that makes it easy to create a RESTful Web Server.

    2. Command-line utility that allows us to run queries against an SQLite database.

    3. Command-line HTTP client written in Python that makes it easy to compose and send HTTP requests.

 

Summary


In this chapter, we designed a RESTful API to interact with a simple SQLite database and perform CRUD operations with games. We defined the requirements for our API and we understood the tasks performed by each HTTP method. We learned the advantages of working with lightweight virtual environments in Python and we set up a virtual environment with Django REST Framework.

We created a model to represent and persist games and we executed migrations in Django. We learned to manage serialization and serialization of game instances into JSON representations with Django REST Framework. We wrote API views to process the different HTTP requests and we configured the URL patterns list to route URLs to views.

Finally, we started the Django development server and we used command-line tools to compose and send HTTP requests to our RESTful API and analyzed how each HTTP request was processed in our code. We also worked with GUI tools to compose and send HTTP requests.

Now that we understand the basics of Django REST Framework, we will expand the capabilities of the RESTful Web API by taking advantage of the advanced features included in the Django REST Framework, which is what we are going to discuss in the next chapter.

About the Author

  • Gaston C. Hillar

    Gaston C. Hillar is Italian and has been working with computers since he was 8 years old. Gaston has a Bachelor's degree in computer science (graduated with honors) and an MBA. Currently, Gaston is an independent IT consultant and a freelance author who is always looking for new adventures anywhere in the world. He was a senior contributing editor at Dr. Dobb's, and has written more than a hundred articles on software development topics. He has received the prestigious Intel Black Belt Software Developer award eight times. He has written many articles about Java for Oracle Java Magazine. Gaston was also a former Microsoft MVP in technical computing. He lives with his wife, Vanesa, and his two sons, Kevin and Brandon.

    Browse publications by this author

Latest Reviews

(9 reviews total)
Testo dettagliato e completo, comprensivo di codice di esempio
Processo de compra muito fácil.
Very good price and purchase process.
Building RESTful Python Web Services
Unlock this book and the full library FREE for 7 days
Start now