Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Django REST Framework

Kevin Harvey
October 23, 2014

Django REST Framework

An introduction to building REST APIs using Django and Django REST Framework.

Kevin Harvey

October 23, 2014
Tweet

More Decks by Kevin Harvey

Other Decks in Technology

Transcript

  1. What’s Django? • Python web framework • Models, views, templates

    • Auth, ORM, automated admin, caching, i18n • “Full featured”
  2. What’s DRF? • Package for Django • Views, authentication and

    utilities for building web APIs • Both highly configurable and low boilerplate • Other options include django-tastypie, django- braces
  3. What is REST? • REpresentational State Transfer • Fielding, Roy

    T. “Architectural Styles and the Design of Network-based Software Architectures”, 2000. Dissertation
  4. SOAP • Simple Object Access protocol • Devil, The. “Java:

    Ruining Deadlines and Getting Developers Fired”, 762. Dissertation • WSDLs
  5. from django.db import models from django.utils.text import slugify ! !

    class BaseModel(models.Model): ! name = models.CharField(max_length=30) slug = models.SlugField(blank=True) ! def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(unicode(self.name)) super(BaseModel, self).save(*args, **kwargs) ! class Meta: abstract = True ! ! class Food(BaseModel): ! quantity = models.IntegerField() unit = models.CharField(max_length=30) ! ! class Recipe(BaseModel): ! ingredients = models.ManyToManyField(Food)
  6. Installation $ pip install djangorestframework ! # settings.py ! INSTALLED_APPS

    = ( … # third party apps ‘rest_framework’, … ) ! $ python manage.py syncdb
  7. Serializers # food/serializers.py ! from rest_framework import serializers ! from

    .models import Food, Recipe ! ! class FoodSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Food fields = ('name', 'quantity', 'unit') ! ! class RecipeSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Recipe fields = ('name', 'ingredients')
  8. Views # food/views.py ! from rest_framework import viewsets ! from

    .models import Food, Recipe from .serializers import FoodSerializer, RecipeSerializer ! ! class FoodViewSet(viewsets.ModelViewSet): """ API endpoint that allows food in the fridge to view viewed or edited """ queryset = Food.objects.all() serializer_class = FoodSerializer ! ! class RecipeViewSet(viewsets.ModelViewSet): """ API endpoint that allows recipes to be viewed or edited """ queryset = Recipe.objects.all() serializer_class = RecipeSerializer
  9. URLs # fridge/urls.py ! from django.conf.urls import patterns, include, url

    ! from rest_framework import routers ! from food import views ! ! router = routers.DefaultRouter() router.register(r'food', views.FoodViewSet) router.register(r'recipes', views.RecipeViewSet) ! urlpatterns = patterns('', url(r'^', include(router.urls)), url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework') ), )
  10. cURL it! #quickstart $ curl -H 'Accept: application/json; indent=4' \

    http://localhost:8000/ { "food": "http://localhost:8000/food/", "recipes": "http://localhost:8000/recipes/" }
  11. Respects HTTP Verbs #quickstart $ curl --request OPTIONS -H 'Accept:

    application/json; indent=4' \ http://localhost:8000/food/ { "name": "Food List", "description": "API endpoint that allows food in the fridge to be viewed or edited", "renders": [ "application/json", "text/html" ], "parses": [ "application/json", "application/x-www-form-urlencoded", "multipart/form-data" ], "actions": { "POST": { "name": { "type": “string" …
  12. Respects Headers #quickstart $ curl -H 'Accept: text/html; indent=4' http://localhost:8000/

    ! ! ! <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta name="robots" content="NONE,NOARCHIVE" /> <title>Django REST framework</title> <link rel="stylesheet" type="text/css" href="/static/ rest_framework/css/bootstrap.min.css"/> <link rel="stylesheet" type="text/css" href=“/static/ rest_framework/css/bootstrap-tweaks.css"/> ! …
  13. With cURL… $ curl -H 'Content-Type: application/json' -d ‘{"name":"Chocolate \

    Syrup", "quantity": 7, "unit": "squirts"}' \ http://localhost:8000/food/ ! {"name": "Chocolate Syrup", ”quantity": 7, "unit": "squirts"}
  14. With cURL… $ curl -H 'Accept: application/json; indent=4' \ http://localhost:8000/food/

    [ { "name": "Milk", "quantity": 24, "unit": "ounces" }, { "name": "Chocolate Syrup", "quantity": 7, "unit": "squirts" } ]
  15. HyperlinkedModelSerializer # food/serializers.py … ! class FoodSerializer(serializers.HyperlinkedModelSerializer): class Meta: model

    = Food fields = ('name', 'quantity', 'unit', 'url') ! ! class RecipeSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Recipe fields = ('name', 'ingredients', 'url') #quickstart-with-url
  16. HyperlinkedModelSerializer $ curl -H 'Accept: application/json; indent=4' \ http://localhost:8000/food/ [

    { "name": "Milk", "quantity": 24, "unit": "ounces", "url": "http://localhost:8000/food/1/" }, { "name": "Chocolate Syrup", "quantity": 7, "unit": "squirts", "url": "http://localhost:8000/food/2/" } ] #quickstart-with-url
  17. Setting Permissions Globally # fridge/settings/base.py … ! REST_FRAMEWORK = {

    'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticatedOrReadOnly', ) } #auth
  18. Permissions on the view # food/views.py ! from rest_framework import

    viewsets, permissions … ! class RecipeViewSet(viewsets.ModelViewSet): """ API endpoint that allows recipes to be viewed or edited """ queryset = Recipe.objects.all() serializer_class = RecipeSerializer permission_classes = (permissions.IsAuthenticated,) #auth
  19. Anonymous Request $ curl -H 'Accept: application/jon; indent=4' \ http://localhost:8000/recipes/

    { "detail": "Authentication credentials were not provided." } #auth
  20. Authenticated Request $ curl -H 'Accept: application/json; indent=4' \ -u

    user:password http://localhost:8000/recipes/ [ { "name": "Chocolate Milk", "ingredients": [ "http://localhost:8000/food/1/", "http://localhost:8000/food/2/" ], "url": "http://localhost:8000/recipes/1/" } ] #auth
  21. Custom Permissions # http://www.django-rest-framework.org/tutorial/4-authentication-and- permissions ! from rest_framework import permissions

    ! ! class IsOwnerOrReadOnly(permissions.BasePermission): """ Custom permission to only allow owners of an object to edit it. """ ! def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True ! # Write permissions are only allowed to the owner of the snippet. return obj.owner == request.user
  22. Versioning • Clients don’t get updated • Typically handled by

    URL • Can also be specified in the header
  23. Documentation • Plan your API before you try to integrate

    • Write docs before you code • Docs inform the tests you write
  24. Documentation $ pip install markdown ! ! class FoodViewSet(viewsets.ModelViewSet): """

    API endpoint that allows food in the fridge to be viewed or edited. ! # Using Markdown in your docs ! - make lists - enumerate things ! Or link to [important stuff][ref] ! [ref]: http://www.django-rest-framework.org/ """ queryset = Food.objects.all() serializer_class = FoodSerializer
  25. Testing • Your API is a promise to your fellow

    developers • Unit testing helps you keep your promises
  26. Testing # food/tests.py ! from rest_framework.test import APITestCase ! from

    .models import Food ! class FoodTestCase(APITestCase): ! def setUp(self): food1 = Food.objects.create( name = 'Leftover Lasagna', quantity = 2, unit = 'pieces' ) ! def test_get_foods(self): response = self.client.get('/food/', format='json') self.assertEqual( response.data[0][‘name'], u'Leftover Lasagna’) #testing