Django Rest Framework
- Why use web APIs?
- Why not just using Django to create APIs?
- What is Django Rest Framework
Model
from django.db import models
class Meal(models.Model):
owner = models.ForeignKey('auth.User', related_name='meals')
name = models.CharField(max_length=255)
notes = models.TextField(blank=True)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Meta:
ordering = ('name',)
Serializer
from rest_framework import serializers
from .models import Meal
class MealSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.Field(source='owner.username')
class Meta:
model = Meal
fields = ('url', 'owner', 'name', 'notes')
- Serializer
- ModelSerializer
- HyperlinkedModelSerializer
-
Third party packages (MongoEngineModelSerializer, GeoFeatureModelSerializer, HStoreSerializer)
Function Based Views
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Meal
from .serializers import MealSerializer
@api_view(['GET', 'POST'])
def meal_list(request):
if request.method == 'GET':
meal = Meal.objects.all()
serializer = MealSerializer(meal, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = MealSerializer(data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Beatiful HTTP Status codes
Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout.
— RFC 2324, Hyper Text Coffee Pot Control Protocol
- Informational - 1xx (eg. HTTP_100_CONTINUE)
- Successful - 2xx (eg. HTTP_200_OK, HTTP_201_CREATED, HTTP_202_ACCEPTED)
- Redirection - 3xx (eg. HTTP_301_MOVED_PERMANENTLY)
- Client Error - 4xx (eg. HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN)
- Server Error - 5xx (eg. HTTP_500_INTERNAL_SERVER_ERROR)
418
I'm a teapot
Class Based Views
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Meal
from .serializers import MealSerializer
class MealList(APIView):
def get(self, request, format=None):
meal = Meal.objects.all()
serializer = MealSerializer(meal, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = MealSerializer(data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Mixins
from rest_framework import mixins
from rest_framework import generics
from .models import Meal
from .serializers import MealSerializer
class MealList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Meal.objects.all()
serializer_class = MealSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Generic Class Based Views
from rest_framework import generics
from .models import Meal
from .serializers import MealSerializer
class MealList(generics.ListCreateAPIView):
queryset = Meal.objects.all()
serializer_class = MealSerializer
But as programmers, we always ask ourselves the same question over and over again.
Very Powerful huh?
Can we do better?
Viewsets
from rest_framework import viewsets
from .models import Meal
from .serializers import MealSerializer
class MealViewSet(viewsets.ModelViewSet):
queryset = Meal.objects.all()
serializer_class = MealSerializer
Routers
from rest_framework.routers import DefaultRouter
from .views import MealViewSet
router = DefaultRouter()
router.register(r'meal', MealViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
Permissions
from rest_framework import permissions, viewsets
from .permissions import IsOwner
class MealViewSet(viewsets.ModelViewSet):
queryset = Meal.objects.all()
serializer_class = MealSerializer
permission_classes = (permissions.IsAuthenticated, IsOwner,)
def pre_save(self, obj):
obj.owner = self.request.user
Permissions
from rest_framework import permissions
class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to view
and edit it.
"""
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
Permissions
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
- pre_save method
- post_save method
- Fitlers
- pagination
- @detail_route
Other useful stuff
Authentication
-
BasicAuthentication
-
TokenAuthentication
-
SessionAuthentication
-
OAuthAuthentication
-
OAuth2Authentication
-
Digest Authentication
-
Django OAuth Toolkit
-
Django OAuth2 Consumer
-
JSON Web Token Authentication
-
Hawk HTTP Authentication
-
HTTP Signature Authentication
Testablity
Code without tests is broken as designed.
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from .models import Meal
class MealViewTest(APITestCase):
def test_add_meal_will_save_the_owner(self):
user_name, user_password, user = create_user()
user_auth(self.client, user_name, user_password)
data = {'name': 'new meal'}
response = self.client.post(
reverse('meal-list'), data, format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['owner'], 'john')
def create_user():
user_name = 'john'
user_email = 'lennon@thebeatles.com'
user_password = 'johnpassword'
user = User.objects.create_user(user_name, user_email, user_password)
return user_name, user_password, user
def user_auth(api_client, user_name, user_password):
api_client.login(username=user_name, password=user_password)
Working with AJAX, CSRF & CORS
https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
https://github.com/ottoyiu/django-cors-headers
RestangularProvider.setDefaultHeaders({"X-CSRFToken": csrftoken});
$http.defaults.headers.common["X-CSRFToken"] = csrftoken;
What do you think?
- Very similar APIs to Django
- Flexible views
- Browseable APIs
- Supports many Authentication policies
- Security
- Testability
- Extensive documentation and great community support.
- Used and trusted by large companies such as Mozilla, Eventbrite and many more (see kickstarter campaign)
-
3.0 is coming
Summary
Questions?
Django REST Framework
By Julio César
Django REST Framework
Why did I choose Django Rest Framework to build my REST APIs.
- 909