Build a Django RESTful API on Fedora.

With the rise of kubernetes and micro-services architecture, being able to quickly write and deploy a RESTful API service is a good skill to have. In this first part of a series of articles, you’ll learn how to use Fedora to build a RESTful application and deploy it on Openshift. Together, we’re going to build the back-end for a “To Do” application.

The APIs allow you to Create, Read, Update, and Delete (CRUD) a task. The tasks are stored in a database and we’re using the Django ORM (Object Relational Mapping) to deal with the database management.

Django App and Rest Framework setup

In a new directory, create a Python 3 virtual environment so that you can install dependencies.

$ mkdir todoapp && cd todoapp
$ python3 -m venv .venv
$ source .venv/bin/activate

After activating the virtual environment, install the dependencies.

(.venv)$ pip install djangorestframework django

Django REST Framework, or DRF, is a framework that makes it easy to create RESTful CRUD APIs. By default it gives access to useful features like browseable APIs, authentication management, serialization of data, and more.

Create the Django project and application

Create the Django project using the django-admin CLI tool provided.

(.venv) $ django-admin startproject todo_app . # Note the trailing '.'
(.venv) $ tree .
.
├── manage.py
└── todo_app
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
1 directory, 5 files

Next, create the application inside the project.

(.venv) $ cd todo_app
(.venv) $ django-admin startapp todo
(.venv) $ cd ..
(.venv) $ tree .
.
├── manage.py
└── todo_app
├── __init__.py
├── settings.py
├── todo
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── urls.py
└── wsgi.py

Now that the basic structure of the project is in place, you can enable the REST framework and the todo application. Let’s add rest_framework and todo to the list of INSTALL_APPS in the project’s settings.py.

todoapp/todo_app/settings.py
# Application definition

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

 Application Model and Database

The next step of building our application is to set up the database. By default, Django uses the SQLite database management system. Since SQLite works well and is easy to use during development, let’s keep this default setting. The second part of this series will look at how to replace SQLite with PostgreSQL to run the application in production.

The Task Model

By adding the following code to todo_app/todo/models.py, you define which properties have a task. The application defines a task with a title, description and a status. The status of a task can only be one of the three following states: Backlog, Work in Progress and Done.

from django.db import models

class Task(models.Model):
STATES = (("todo", "Backlog"), ("wip", "Work in Progress"), ("done", "Done"))
title = models.CharField(max_length=255, blank=False, unique=True)
description = models.TextField()
status = models.CharField(max_length=4, choices=STATES, default="todo")

Now create the database migration script that Django uses to update the database with changes.

(.venv) $ PYTHONPATH=. DJANGO_SETTINGS_MODULE=todo_app.settings django-admin makemigrations

Then you can apply the migration to the database.

(.venv) $ PYTHONPATH=. DJANGO_SETTINGS_MODULE=todo_app.settings django-admin migrate

This step creates a file named db.sqlite3 in the root directory of the application. This is where SQLite stores the data.

Access to the data

Creating a View

Now that you can represent and store a task in the database, you need a way to access the data.  This is where we start making use of Django REST Framework by using the ModelViewSet. The ModelViewSet provides the following actions on a data model: list, retrieve, create, update, partial update, and destroy.

Let’s add our view to todo_app/todo/views.py:

from rest_framework import viewsets

from todo_app.todo.models import Task
from todo_app.todo.serializers import TaskSerializer


class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer

Creating a Serializer

As you can see, the TaskViewSet is using a Serializer. In DRF, serializers convert the data modeled in the application models to a native Python datatype. This datatype can be later easily rendered into JSON or XML, for example. Serializers are also used to deserialize JSON or other content types into the data structure defined in the model.

Let’s add our TaskSerializer object by creating a new file in the project todo_app/todo/serializers.py:

from rest_framework.serializers import ModelSerializer
from todo_app.todo.models import Task


class TaskSerializer(ModelSerializer):
class Meta:
model = Task
fields = "__all__"

We’re using the generic ModelSerializer from DRF, to automatically create a serializer with the fields that correspond to our Task model.

Now that we have a data model a view and way to serialize/deserialize data, we need to map our view actions to URLs. That way we can use HTTP methods to manipulate our data.

Creating a Router

Here again we’re using the power of the Django REST Framework with the DefaultRouter. The DRF DefaultRouter takes care of mapping actions to HTTP Method and URLs.

Before we see a better example of what the DefaultRouter does for us, let’s add a new URL to access the view we have created earlier. Add the following to todo_app/urls.py:

from django.contrib import admin
from django.conf.urls import url, include

from rest_framework.routers import DefaultRouter

from todo_app.todo.views import TaskViewSet

router = DefaultRouter()
router.register(r"todo", TaskViewSet)

urlpatterns = [
url(r"admin/", admin.site.urls),
url(r"^api/", include((router.urls, "todo"))),
]

As you can see, we’re registering our TaskViewSet to the DefaultRouter. Then later, we’re mapping all the router URLs to the /api endpoint. This way, DRF takes care of mapping the URLs and HTTP method to our view actions (list, retrieve, create, update, destroy).

For example, accessing the api/todo endpoint with a GET HTTP request calls the list action of our view. Doing the same but using a POST HTTP request calls the create action.

To get a better grasp of this, let’s run the application and start using our API.

Running the application

We can run the application using the development server provided by Django. This server should only be used during development. We’ll see in the second part of this tutorial how to use a web server better suited for production.

(.venv)$ PYTHONPATH=. DJANGO_SETTINGS_MODULE=todo_app.settings django-admin runserver
Django version 2.1.5, using settings 'todo_app.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Now we can access the application at the following URL: http://127.0.0.1:8000/api/

DRF provides an interface to the view actions, for example listing or creating tasks, using the following URL: http://127.0.0.1:8000/api/todo

Or updating/deleting an existing tasks with this URL: http://127.0.0.1:8000/api/todo/1

Conclusion

In this article you’ve learned how to create a basic RESTful API using the Django REST Framework. In the second part of this series, we’ll update this application to use the PostgreSQL database management system, and deploy it in OpenShift.

The source code of the application is available on GitHub.


Fedora Project community

15 Comments

  1. jwm

    Great article! Django and DRF rule. Would be nice to see a demonstration of OpenAPI/Swagger (from e.g. drf-yasg + pyswagger etc), considering how easy it is to get going.

  2. Manuel Funes

    Thanks!

  3. Emilio Ferreyra

    I’m a little bit confused. Why do you use PYTHONPATH=. DJANGO_SETTINGS_MODULE=todo_app.settings django-admin makemigrations instead of only the command makemigrations?

    • @Emilio you need to provide to the django-admin makemigrations command the configuration file of django so that it can correctly connect to the database and apply the migration that’s the DJANGO_SETTINGS_MODULE environment variable. Then for the python interpreter to be able to correctly import the configuration file you need to set the PYTHONPATH variable to be the current directory ‘.’.

      Does that help you understand it ?

  4. Marcelo

    Excellent article simple and easy to understand, I would like to see an article with API with authentication. hugs

  5. Serge Meeuwsen

    Something is wrong with this article. It makes a lot of assumptions on users prior knowledge of django without specifically stating so.
    Executing the code as given fails at this step:
    DJANGO_SETTINGS_MODULE=todo_app.settings django-admin migrate

    With error:
    django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.

    DIdn’t we just set the env variable??

    Followed all the steps exactly in Fedora 29…

    • Emilio Ferreyra

      Try executing python manage.py makemigrations and python manage.py migrate…

      • Serge Meeuwsen

        Thanks Emilio, yes indeed and that’s what I’ve done and that actually worked. I just wanted to make the poster aware that the article contains an error in the Task Model section. The last two code snippets in that section are exact copies of each other so it seems something is missing there…

  6. Raz

    Great tutorial.
    I see the following error when invoking the task ID 1:

    Page not found (404)
    Request Method: GET
    Request URL: http://127.0.0.1:8000/api/todo/2
    Using the URLconf defined in todo_app.urls, Django tried these URL patterns, in this order:
    ^admin/
    ^api/ ^todo/$ [name=’task-list’]
    ^api/ ^todo.(?P[a-z0-9]+)/?$ [name=’task-list’]
    ^api/ ^todo/(?P[^/.]+)/$ [name=’task-detail’]
    ^api/ ^todo/(?P[^/.]+).(?P[a-z0-9]+)/?$ [name=’task-detail’]
    ^api/ ^$ [name=’api-root’]
    ^api/ ^.(?P[a-z0-9]+)/?$ [name=’api-root’]
    The current path, api/todo/2, didn’t match any of these.

    Not sure how to troubleshoot that, thanks =)

  7. Mehdi

    I followed the article. The process you described was straightforward and worked, but I barely understood anything. I come from PHP. Is python for web development always this hard?

  8. Nico

    Missing tests !!!! 🙂

  9. jerry

    the code to add
    ‘django.contrib.staticfiles’,
    ‘rest_framework’,
    to the INSTALLED_APPS should be
    cd todo_app/
    vi settings.py
    There was a cd .. prior to this.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The opinions expressed on this website are those of each author, not of the author's employer or of Red Hat. Fedora Magazine aspires to publish all content under a Creative Commons license but may not be able to do so in all cases. You are responsible for ensuring that you have the necessary permission to reuse any work on this site. The Fedora logo is a trademark of Red Hat, Inc. Terms and Conditions

%d bloggers like this: