Django REST Framework

The Good, the Bad and the Ugly

Nick Moore / nick@zoic.org

Django REST Framework

django-rest-framework.org



Versions

  • Python 2.7
  • Django 1.6
  • djangorestframework 2.3.10

REST?

Representational State Transfer



CRUD?

Create, Retrieve, Update, Destroy

Why REST?

  • Native clients & HTML5/AJAX clients
  • REST resources map to Django model entities
  • Stateless, CRUD operations suit HTTP
  • GETs are cacheable
  • Very widely used, familiar, abstractable

Why not REST?

  • May drive front-end developers mad
    • many calls
    • asynchronicity
  • No way to bind multiple operations into a single transaction

Django REST Framework

Good

  • Documentation
  • Little Code Needed
  • Built-in API Interface
  • Extensibility

Documentation

  • Website docs are excellent
  • Code quite readable
  • Lots of discussion on StackOverflow, etc

Little Code Needed (1)

models.py


from django.db import models
from django.contrib.auth.models import User


class Stuff(models.Model):

    name = models.TextField()


class Things(models.Model):

    name = models.TextField()
    user = models.ForeignKey(User, related_name="things_set")
    stuff_set = models.ManyToManyField(Stuff)
            

Models are unchanged, so you can add a REST interface to an existing project easily

Little Code Needed (2)

settings.py


INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'example.main'
)

REST_FRAMEWORK = {
}
            
  • Add 'rest_framework' to your installed apps.
  • REST_FRAMEWORK allows setting defaults, etc.
  • You probably also want 'rest_framework.authtoken'

Little Code Needed (3)

serializers.py


from example.main.models import *
from rest_framework import serializers

import django.db.models


class StuffSerializer(serializers.ModelSerializer):

    class Meta:
        model = Stuff
        fields = ('id', 'name', 'things_set')


class ThingsSerializer(serializers.ModelSerializer):

    class Meta:
        model = Things
        fields = ('id', 'name', 'user', 'stuff_set')
            
  • Serializers are the translator between Django objects and native python
  • Actual serialization and deserialization is done by Renderers / Parsers ... can be JSON or XML or YAML (etc)

Little Code Needed (4)

views.py


from rest_framework import viewsets

from example.main.models import *
from example.main.serializers import *

class ThingsViewSet(viewsets.ModelViewSet):
    model = Things
    serializer_class = ThingsSerializer

    def get_queryset(self):
        return self.request.user.things_set.all()

class StuffViewSet(viewsets.ModelViewSet):
    model = Stuff
    serializer_class = StuffSerializer
            

ViewSets bind together the CRUD operations for a resource

GET, POST, PUT, PATCH, DELETE, OPTIONS

Little Code Needed (5)

urls.py


from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

from rest_framework import routers

from example.main import views

api_router = routers.DefaultRouter()
api_router.register(r'things', views.ThingsViewSet)
api_router.register(r'stuff', views.StuffViewSet)

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

Router collects the ViewSets into a neat API

Built-in API Interface (1)

  • Not a replacement for /admin/ but similar
  • Lists views, lists objects, view objects, edit objects

Built-in API Interface (2)

List ViewSets

Built-in API Interface (3)

List & Create Resources

Built-in API Interface (4)

View & Delete Resources

Built-in API Interface (5)

Update Resources

Extensible (1)

Classes are easy to inherit from and extend

  • ModelSerializer
    • from_native
    • to_native
    • __init__
    • field_mapping

  • ModelViewSet
    • get_queryset
    • "actions"

  • ViewSets are just CBVs

Extensible (2)

Serializers


class CommaSeparatedIntegerSerializerField(serializers.CharField):
    """This just makes the representation of a CommaSeparatedIntegerField a bit
    more JSON friendly by splitting it out and putting it back together"""

    def to_native(self, value):
        if not value: return []
        return [ int(v) for v in value.split(',') ]

    def from_native(self, value):
        if value is None: return None
        return ','.join([str(v) for v in value])
          

Extensible (3)

Viewsets


class UserViewSet(viewsets.ModelViewSet):
    model = User
    serializer_class = UserSerializer

    def get_queryset(self):
        return User.objects.filter(id=self.request.user.id).prefetch_related('things_set')
            

The Bad (but fixable)

  • Unknown filter_fields are ignored
  • Efficiency requires liberal use of .prefetch_related()
  • Slightly odd behaviours around many-to-many relationships many=True, required=False, allow_add_remove=True
  • Lots of repeating yourself, across multiple files (Model, Serializers, ViewSets, URLs)

The Ugly: Hidden Complexity

  • Complicated Class Hierarchy with Mixins and Metaclasses
  • Exceptions happen deep in the framework code
  • May end up needing multiple Serializers per Model
  • filtering / searching URLs aren't elegant

Alternatives

?