How to quickly write an API in Django

April 13, 2015 in Django


I designed drf-generators to allow you (the developer) to spend less time worrying about views and serializers and more time creating rock-solid data models for your application.

If you know a bit about Django, you can create a basic API in minutes. This tutorial will walk you through creating a complete API using the Django rest framework and drf-generators to jump-start your development.


1. Set up your Django project

First we need to set up our django application. My recommendation with every new project is to create a virtualenv to manage your depnedencies.

$ virtualenv -p python3 venv
$ source venv/bin/activate

Next, install drf-generators. This installation will also install its dependencies, including Django and django-rest-framework.

$ pip install drf-generators

Create a Django project and api app.

$ django-admin.py startproject example_api
$ cd example_api
$ python manage.py startapp api

Finally add "api", "rest_framework", and "drf_generators" to the INSTALLED_APPS in your settings

INSTALLED_APPS = (
    ...
    'rest_framework',
    'drf_generators',
    'api',
)

Now our project is all set up and ready to go!


2. Define your API models

Now that the environment is set up, we can begin constructing the application. Let's start with the models. For this example, I will be designing a basic blogging system with Entries and Cateogries. Feel free to follow along with your own models.

The slug field in each of the models is for urls. When they are saved in the Django admin, slugify will create a "lisp-case" (i.e. /how-to-do-the-thing) slug for use in urls.

# api/models.py
from django.template.defaultfilters import slugify
from django.contrib.auth.models import User
from django.db import models


class Category(models.Model):
    name = models.CharField(max_length=32)
    slug = models.SlugField(max_length=32, default='', blank=True)
    parent = models.ForeignKey('self', blank=True, null=True)

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    class Meta:
        verbose_name_plural = 'Categories'


class Entry(models.Model):
    title = models.CharField(max_length=64)
    slug = models.SlugField(max_length=32, default='', blank=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    content = models.TextField()
    category = models.ForeignKey(Category)

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super().save(*args, **kwargs)

    class Meta:
        verbose_name_plural = 'Entries'

Additionally, let's go ahead and register the models with the admin so we can enter some test data

# api/admin.py
from django.contrib import admin
from api.models import Category, Entry

admin.site.register(Category)
admin.site.register(Entry)

3. Make Migrations and load initial data

Now that we have defined the models, let's make our migrations

$ python manage.py makemigrations
    # Migrations for 'api':
    # 0001_initial.py:
        # - Create model Category
        # - Create model Entry

$ python manage.py migrate
    # Applying ..... OK
    # Applying api.0001_initial... OK

Now we can load some initial data. To do this save the following json in data.json in your project root and load it using the 'loaddata' command.

$ python manage.py loaddata data.json
    > Installed 4 object(s) from 1 fixture(s)

data.json

[
   {
      "fields":{
         "slug":"programming",
         "name":"Programming",
         "parent":null
      },
      "model":"api.category",
      "pk":1
   },
   {
      "fields":{
         "slug":"python",
         "name":"Python",
         "parent":1
      },
      "model":"api.category",
      "pk":2
   },
   {
      "fields":{
         "slug":"django",
         "name":"Django",
         "parent":2
      },
      "model":"api.category",
      "pk":3
   },
   {
      "fields":{
         "updated":"2015-04-13T17:52:38.404Z",
         "slug":"writing-an-api-in-django",
         "title":"Writing an API in Django",
         "category":3,
         "created":"2015-04-13T17:52:38.404Z",
         "content":"Django is pretty neat, eh."
      },
      "model":"api.entry",
      "pk":1
   }
]

4. Generate the API

Now that the models are well defined, we can generate and start working with the API. Drf-generators makes creating Views, Serializers and URLs dead simple. Simply run the following command. Here we are going to use the "modelviewset" format for simplicity. Other formats include "viewset", "function", and "apiview". If you have existing files, it ask you if you want to overwrite them.

$ python manage.py generate api --format modelviewset
    # Are you sure you want to overwrite views.py? (y/n): y
        # - writing serializers.py
        # - writing views.py
        # - writing urls.py

Sweet! Now we have a base to work on. Let's examine the generated files.

Serializers

The serializers.py file contains classes that extends rest_framework's ModelSerializer. These classes define how the models are serialized into JSON. The Meta class defines the model that will be serialized. This is a basic as ModelSerializers come. For more options and customization, check out the Django Rest Framework documentation

# api/serializers.py
from rest_framework.serializers import ModelSerializer
from api.models import Category, Entry


class CategorySerializer(ModelSerializer):

    class Meta:
        model = Category


class EntrySerializer(ModelSerializer):

    class Meta:
        model = Entry

Views

The views.py contains your API views. This is where the magic happens. Since we used the "modelviewset" format, our Views are based on rest_framework's ModelViewSet class. With the ModelViewSet class, all we have to do is define the queryset and serializer class and rest_framework does the rest. The views support get, post, put, delete, and list methods that we will explore later

# api/views.py
from rest_framework.viewsets import ModelViewSet
from api.serializers import CategorySerializer, EntrySerializer
from api.models import Category, Entry


class CategoryViewSet(ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer


class EntryViewSet(ModelViewSet):
    queryset = Entry.objects.all()
    serializer_class = EntrySerializer

Urls

Lastly the generators created a urls.py that contains the API routes. The routes bind the url to the corresponding ViewSet for each model.

# api/urls.py
from rest_framework.routers import SimpleRouter
from api import views


router = SimpleRouter()

router.register(r'category', views.CategoryViewSet)
router.register(r'entry', views.EntryViewSet)

urlpatterns = router.urls

5. Testing the API

The last thing we need to do before testing the api, is reference the API's urls from the project's urls. To do this, add an api route so your urls look like the following

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

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

Now it's time to run the project and test the api. To spin up the local development server execute the runserver command

$ python manage.py runserver

Now navigate to http://127.0.0.1:8000/api/entry/. You should be presented with the rest_framework's browsable API with a list of Entries. Append '?format=json' to the url and your output should look like the following.

[
    {
        "id": 1,
        "title": "Writing an API in Django",
        "slug": "writing-an-api-in-django",
        "created": "2015-04-13T17:52:38.404000Z",
        "updated": "2015-04-13T17:52:38.404000Z",
        "content": "Django is pretty neat, eh.",
        "category": 3
    }
]

If your output looks like this, congrats! You have successfully created a simple API with drf-generators.

Thanks for following along this tutorial. Drf-generators is a side-project of mine that I am constantly working to improve. If you come across an issue or bug, please submit an issue on Github. If you find a way to make drf-generators or would like to contribute, feel free to fork it and submit a pull request.