19 min read

In this article, we will kick start with learning RESTful Web Service and subsequently learn how to create models and perform migration, serialization and deserialization in Django. Here’s a quick glance of what to expect in this article:

  • Defining the requirements for RESTful Web Service
  • Analyzing and understanding Django tables and the database
  • Controlling, serialization and deserialization in Django

Defining the requirements for RESTful Web Service

Imagine a team of developers working on a mobile app for iOS and Android and requires a RESTful Web Service to perform CRUD operations with toys. We definitely don’t want to use a mock web service and we don’t want to spend time choosing and configuring an ORM (short for Object-Relational Mapping). We want to quickly build a RESTful Web Service and have it ready as soon as possible to start interacting with it in the mobile app.

We really want the toys to persist in a database but we don’t need it to be production-ready. Therefore, we can use the simplest possible relational database, as long as we don’t have to spend time performing 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 the 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 toy. We need the following attributes or fields for a toy entity:

  • An integer identifier
  • A name
  • An optional description
  • A toy category description, such as action figures, dolls, or playsets
  • A release date
  • A bool value indicating whether the toy has been on the online store’s homepage at least once

In addition, we want to have a timestamp with the date and time of the toy’s addition to the database table, which will be generated to persist toys.

In a RESTful Web Service, each resource has its own unique URL. In our web service, each toy will have its own unique URL.

The following table shows the HTTP verbs, the scope, and the semantics of the methods that our first version of the web service must support. Each method is composed of an HTTP verb and a scope. All the methods have a well-defined meaning for toys and collections:

HTTP verb Scope Semantics
GET Toy Retrieve a single toy
GET Collection of toys Retrieve all the stored toys in the collection, sorted by their name in ascending order
POST Collection of toys Create a new toy in the collection
PUT Toy Update an existing toy
DELETE Toy Delete an existing toy

In the previous table, the GET HTTP verb appears twice but with two different scopes: toys and collection of toys. The first row shows a GET HTTP verb applied to a toy, that is, to a single resource. The second row shows a GET HTTP verb applied to a collection of toys, that is, to a collection of resources.

We want our web service to be able to differentiate collections from a single resource of the collection in the URLs. When we refer to a collection, we will use a slash (/) as the last character for the URL, as in http://localhost:8000/toys. When we refer to a single resource of the collection we won’t use a slash (/) as the last character for the URL, as in http://localhost:8000/toys/5.

Let’s consider that http://localhost:8000/toys/ is the URL for the collection of toys. If we add a number to the previous URL, we identify a specific toy with an ID or primary key equal to the specified numeric value. For example, http://localhost:8000/toys/42 identifies the toy with an ID equal to 42.

We have to compose and send an HTTP request with the POST HTTP verb and http://localhost:8000/toys/ request URL to create a new toy and add it to the toys collection. In this example, our RESTful Web Service will work with JSON (short for JavaScript Object Notation), and therefore we have to provide the JSON key-value pairs with the field names and the values to create the new toy. As a result of the request, the server will validate the provided values for the fields, make sure that it is a valid toy, and persist it in the database. The server will insert a new row with the new toy in the appropriate table and it will return a 201 Created status code and a JSON body with the recently added toy serialized to JSON, including the assigned ID that was automatically generated by the database and assigned to the toy object:

POST http://localhost:8000/toys/

We have to compose and send an HTTP request with the GET HTTP verb and http://localhost:8000/toys/{id} request URL to retrieve the toy whose ID matches the specified numeric value in {id}. For example, if we use the request URL http://localhost:8000/toys/25, the server will retrieve the toy whose ID matches 25. As a result of the request, the server will retrieve a toy with the specified ID from the database and create the appropriate toy object in Python. If a toy is found, the server will serialize the toy object into JSON, return a 200 OK status code, and return a JSON body with the serialized toy object. If no toy matches the specified ID, the server will return only a 404 Not Found status:

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

We have to compose and send an HTTP request with the PUT HTTP verb and request URL http://localhost:8000/toys/{id} to retrieve the toy whose ID matches the value in {id} and replace it with a toy 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 toy 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 toy, and replace the one that matches the specified ID with the new one in the database. The ID for the toy 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 toy serialized to JSON. If we don’t provide all the necessary data for the new toy, the server will return a 400 Bad Request status code. If the server doesn’t find a toy with the specified ID, the server will only return a 404 Not Found status:

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

We have to compose and send an HTTP request with the DELETE HTTP verb and request URL http://localhost:8000/toys/{id} to remove the toy whose ID matches the specified numeric value in {id}. For example, if we use the request URL http://localhost:8000/toys/34, the server will delete the toy whose ID matches 34. As a result of the request, the server will retrieve a toy with the specified ID from the database and create the appropriate toy object in Python. If a toy is found, the server will request the ORM delete the toy row associated with this toy object and the server will return a 204 No Content status code. If no toy matches the specified ID, the server will return only a 404 Not Found status:

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

Creating first Django model

Now, we will create a simple Toy model in Django, which we will use to represent and persist toys. Open the toys/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 Toy class, specifically, a Toy model in the toys/models.py file. The code file for the sample is included in the hillar_django_restful_02_01 folder in the restful01/toys/models.py file:

from django.db import models

class Toy(models.Model):

created = models.DateTimeField(auto_now_add=True)

name = models.CharField(max_length=150, blank=False, default='') description = models.CharField(max_length=250, blank=True, default='') toy_category = models.CharField(max_length=200, blank=False,

default='')

release_date = models.DateTimeField() was_included_in_home = models.BooleanField(default=False)

class Meta:

ordering = ('name',)

The Toy class is a subclass of the django.db.models.Model class and defines the following attributes: created, name, description, toy_category, release_date, and was_included_in_home. Each of these attributes represents a database column or field.

We specified the field types, maximum lengths, and defaults for many attributes. The class declares a Meta inner class that declares an ordering attribute and sets its value to a tuple of string whose first value is the ‘name’ string. This way, the inner class indicates to Django that, by default, we want the results ordered by the name attribute in ascending order.

Running initial migration

Now, it is necessary to create the initial migration for the new Toy model we recently coded. We will also synchronize the SQLite database for the first time. By default, Django uses the popular self-contained and embedded SQLite database, and therefore we don’t need to make changes in the initial ORM configuration. In this example, we will be working with this default configuration. Of course, we will upgrade to another database after we have a sample web service built with Django. We will only use SQLite for this example.

We just need to run the following Python script in the virtual environment that we activated in the previous chapter. Make sure you are in the restful01 folder within the main folder for the virtual environment when you run the following command:

python manage.py makemigrations toys

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

Migrations for 'toys': toys/migrations/0001_initial.py:

- Create model Toy

The output indicates that the restful01/toys/migrations/0001_initial.py file includes the code to create the Toy 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 hillar_django_restful_02_01 folder in the restful01/toys/migrations/0001_initial.py file:

# -*- coding: utf-8 -*-

# Generated by Django 1.11.5 on 2017-10-08 05:19

from   future   

import unicode_literals

from django.db import migrations, models

class Migration(migrations.Migration): initial = True

dependencies = [

]

operations = [ migrations.CreateModel(

name='Toy', 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(default='', max_length=150)), ('description', models.CharField(blank=True, default='',
max_length=250)),

('toy_category', models.CharField(default='', max_length=200)),

('release_date', models.DateTimeField()), ('was_included_in_home',

models.BooleanField(default=False)),

],

options={

'ordering': ('name',),

},

),

]

Understanding migrations

The automatically generated code defines a subclass of the django.db.migrations.Migration class named Migration, which defines an operation that creates the Toy model’s table and includes it in the operations attribute. The call to the migrations.CreateModel method specifies the model’s name, the fields, and the options to instruct the ORM to create a table that will allow the underlying database to persist the model.

The fields argument is a list of tuples that includes information about the field name, the field type, and additional attributes based on the data we provided in our model, that is, in the Toy class.

Now, run the following Python script to apply all the generated migrations. Make sure you are in the restful01 folder within the main folder for the virtual environment when you run the following command:

python manage.py migrate

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

Operations to perform:

Apply all migrations: admin, auth, contenttypes, sessions, toys Running migrations:

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 auth.0008_alter_user_username_max_length... OK Applying sessions.0001_initial... OK

Applying toys.0001_initial... OK

After we run the previous command, we will notice that the root folder for our restful01 project now has a db.sqlite3 file that contains the SQLite database. 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.

The first migration will generate many tables required by Django and its installed apps before running the code that creates the table for the Toys model. These tables provide support for user authentication, permissions, groups, logs, and migration management. We will work with the models related to these tables after we add more features and security to our web services.

After the migration process creates all these Django tables in the underlying database, the first migration runs the Python code that creates the table required to persist our model. Thus, the last line of the running migrations section displays Applying toys.0001_initial.

Analyzing the database

In most modern Linux distributions and macOS, SQLite is already installed, and therefore you can run the sqlite3 command-line utility.

In Windows, if you want to work with the sqlite3.exe command-line utility, you have to download the bundle of command-line tools for managing SQLite database files from the downloads section of the SQLite webpage at http://www.sqlite.org/download.html. For example, the ZIP file that includes the command-line tools for version 3.20.1 is sqlite- tools-win32-x8 6-3200100.zip. The name for the file changes with the SQLite version. You just need to make sure that you download the bundle of command-line tools and not the ZIP file that provides the SQLite DLLs. After you unzip the file, you can include the folder that includes the command-line tools in the PATH environment variable, or you can access the sqlite3.exe command-line utility by specifying the full path to it.

Run the following command to list the generated tables. The first argument, db.sqlite3, specifies the file that contains that SQLite database and the second argument indicates the command that we want the sqlite3 command-line utility to run against the specified database:

sqlite3 db.sqlite3 ".tables"

The following lines show the output for the previous command with the list of tables that Django generated in the SQLite database:

auth_group                django_admin_log auth_group_permissions                          django_content_type auth_permission                         django_migrations

auth_user                 django_session

auth_user_groups                      toys_toy auth_user_user_permissions

The following command will allow you to check the contents of the toys_toy table after we compose and send HTTP requests to the RESTful Web Service and the web service makes CRUD operations to the toys_toy table:

sqlite3 db.sqlite3 "SELECT * FROM toys_toy 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, free, multiplatform GUI tool that allows us to easily check the database contents of an SQLite database in Linux, macOS, and Windows. You can read more information about this tool and download its different versions from http://sqlitebrowser.org. Once you have 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. After we start working with the first version of our web service, you need to check the contents of the toys_toy table with this tool

The SQLite database engine and the database file name are specified in the restful01/settings.py Python file. The following lines show the declaration of the DATABASES dictionary, which contains the settings for all the databases 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 (restful01):

DATABASES = {

'default': {

'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),

}

}

After we execute the migrations, the SQLite database will have the following tables. Django uses prefixes to identify the modules and applications that each table belongs to. The tables that start with the auth_ prefix belong to the Django authentication module. The table that starts with the toys_ prefix belongs to our toys application. If we add more models to our toys application, Django will create new tables with the toys_ prefix:

auth_group: Stores authentication groups

auth_group_permissions: Stores permissions for authentication groups

auth_permission: Stores permissions for authentication auth_user: Stores authentication users auth_user_groups: Stores authentication user groups

auth_user_groups_permissions: Stores permissions for authentication user groups

django_admin_log: Stores the Django administrator log

django_content_type: Stores Django content types

django_migrations: Stores the scripts generated by Django migrations and the date and time at which they were applied

django_session: Stores Django sessions

toys_toy: Persists the Toys model

sqlite_sequence: Stores sequences for SQLite primary keys with autoincrement fields

Understanding the table generated by Django

The toys_toy table persists in the database the Toy class we recently created, specifically, the Toy model. Django’s integrated ORM generated the toys_toy table based on our Toy model.

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

sqlite3 db.sqlite3 ".schema toys_toy"

The following lines show the output for the previous command together with the SQL that the migrations process executed, to create the toys_toy table that persists the Toy model. The next lines are formatted to make it easier to understand the SQL code. Notice that the output from the command is formatted in a different way:

CREATE TABLE IF NOT EXISTS "toys_toy" (

"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,

"created" datetime NOT NULL, "name" varchar(150) NOT NULL,

"description" varchar(250) NOT NULL, "toy_category" varchar(200) NOT NULL, "release_date" datetime NOT NULL, "was_included_in_home" bool NOT NULL

);

The toys_toy table has the following columns (also known as fields) with their SQLite types, all of them not nullable:

  • id: The integer primary key, an autoincrement row
  • created: DateTime
  • name: varchar(150)
  • description: varchar(250)
  • toy_category: varchar(200)
  • release_date: DateTime
  • was_included_in_home: bool

Controlling, serialization and deserialization

Our RESTful Web Service has to be able to serialize and deserialize the Toy instances into JSON representations. In Django REST framework, we just need to create a serializer class for the Toy instances to manage serialization to JSON and deserialization from JSON. Now, we will dive deep into the serialization and deserialization process in Django REST framework. It is very important to understand how it works because it is one of the most important components for all the RESTful Web Services we will build.

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 Toy 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 Toy 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, which will reduce boilerplate code later in the following examples. We will write less code in the following examples by using the ModelSerializer class.

Now, go to the restful01/toys folder and create a new Python code file named serializers.py. The following lines show the code that declares the new ToySerializer class. The code file for the sample is included in the hillar_django_restful_02_01 folder in the restful01/toys/serializers.py file:

from rest_framework import serializers from toys.models import Toy

class ToySerializer(serializers.Serializer):

pk = serializers.IntegerField(read_only=True) name = serializers.CharField(max_length=150)

description = serializers.CharField(max_length=250) release_date = serializers.DateTimeField() toy_category = serializers.CharField(max_length=200)

was_included_in_home = serializers.BooleanField(required=False)

def create(self, validated_data):

return Toy.objects.create(**validated_data)

def update(self, instance, validated_data):

instance.name = validated_data.get('name', instance.name) instance.description = validated_data.get('description',

instance.description)

instance.release_date = validated_data.get('release_date', instance.release_date)

instance.toy_category = validated_data.get('toy_category', instance.toy_category)

instance.was_included_in_home = validated_data.get('was_included_in_home', instance.was_included_in_home)

instance.save() return instance

The ToySerializer class declares the attributes that represent the fields that we want to be serialized. Notice that we have omitted the created attribute that was present in the Toy model. When there is a call to the save method that ToySerializer inherits from the serializers.Serializer superclass, the overridden create and update methods define how to create a new instance or update an existing instance. In fact, these methods must be implemented in our class because they only raise a NotImplementedError exception in their base declaration in the serializers.Serializer superclass.

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

The update method receives an existing Toy 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. Finally, the code calls the save method for the updated Toy instance and returns the updated and saved instance.

We designed a RESTful Web Service to interact with a simple SQLite database and perform CRUD operations with toys. We defined the requirements for our web service and understood the tasks performed by each HTTP method and different scope.

You read an excerpt from the book, Django RESTful Web Services, written by Gaston C. Hillar. This book helps developers build complex RESTful APIs from scratch with Django and the Django REST Framework. The code bundle for the article is hosted on GitHub.

Django RESTful Web Services

 

Content Marketing Editor at Packt Hub. I blog about new and upcoming tech trends ranging from Data science, Web development, Programming, Cloud & Networking, IoT, Security and Game development.

LEAVE A REPLY

Please enter your comment!
Please enter your name here