Slide 1

Slide 1 text

Django REST Framework Building a web API with Django Kevin Harvey @kevinharvey [email protected]

Slide 2

Slide 2 text

What are we doing?

Slide 3

Slide 3 text

What are we doing? • Introductions • Implementing Django REST Framework • API best practices

Slide 4

Slide 4 text

Kevin’s Fridge Sample app: http://kevins-fridge.herokuapp.com/ ! user:apassword superuser:thepassword ! Code: https://github.com/kcharvey/fridge

Slide 5

Slide 5 text

Introductions

Slide 6

Slide 6 text

Who is this guy? • Senior Systems Engineer at SmileCareClub • Djangonaut since 2007 (0.96)

Slide 7

Slide 7 text

Who are you guys? A quick poll…

Slide 8

Slide 8 text

What’s Django?

Slide 9

Slide 9 text

What’s Django? • Python web framework • Models, views, templates • Auth, ORM, automated admin, caching, i18n • “Full featured”

Slide 10

Slide 10 text

What’s Django REST Framework?

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

What is REST?

Slide 13

Slide 13 text

What is REST? • REpresentational State Transfer • Fielding, Roy T. “Architectural Styles and the Design of Network-based Software Architectures”, 2000. Dissertation

Slide 14

Slide 14 text

What is REST? • Client-Server • Stateless • JSON, XML, etc. • HATEOAS

Slide 15

Slide 15 text

Alternatives?

Slide 16

Slide 16 text

SOAP • Simple Object Access protocol • Devil, The. “Java: Ruining Deadlines and Getting Developers Fired”, 762. Dissertation • WSDLs

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Web Sockets

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Implementing Django REST Framework

Slide 21

Slide 21 text

Assumptions • Django project • Some models

Slide 22

Slide 22 text

Kevin’s Fridge Sample app: http://kevins-fridge.herokuapp.com/ ! user:apassword superuser:thepassword ! Code: https://github.com/kcharvey/fridge

Slide 23

Slide 23 text

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)

Slide 24

Slide 24 text

Installation $ pip install djangorestframework ! # settings.py ! INSTALLED_APPS = ( … # third party apps ‘rest_framework’, … ) ! $ python manage.py syncdb

Slide 25

Slide 25 text

Code It Up • Serializers • Views • URLs

Slide 26

Slide 26 text

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')

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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') ), )

Slide 29

Slide 29 text

So what did we get? #quickstart

Slide 30

Slide 30 text

#quickstart

Slide 31

Slide 31 text

#quickstart

Slide 32

Slide 32 text

#quickstart

Slide 33

Slide 33 text

#quickstart

Slide 34

Slide 34 text

#quickstart

Slide 35

Slide 35 text

cURL it! #quickstart $ curl -H 'Accept: application/json; indent=4' \ http://localhost:8000/ { "food": "http://localhost:8000/food/", "recipes": "http://localhost:8000/recipes/" }

Slide 36

Slide 36 text

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" …

Slide 37

Slide 37 text

Respects Headers #quickstart $ curl -H 'Accept: text/html; indent=4' http://localhost:8000/ ! ! ! Django REST framework ! …

Slide 38

Slide 38 text

Let’s add some stuff #quickstart

Slide 39

Slide 39 text

#quickstart

Slide 40

Slide 40 text

#quickstart

Slide 41

Slide 41 text

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"}

Slide 42

Slide 42 text

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" } ]

Slide 43

Slide 43 text

Putting the ‘H’ in HTML #quickstart-with-url

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

#quickstart-with-url

Slide 47

Slide 47 text

Let’s lock this down a bit #auth

Slide 48

Slide 48 text

Setting Permissions Globally # fridge/settings/base.py … ! REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticatedOrReadOnly', ) } #auth

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Anonymous Request $ curl -H 'Accept: application/jon; indent=4' \ http://localhost:8000/recipes/ { "detail": "Authentication credentials were not provided." } #auth

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

API Best Practices

Slide 54

Slide 54 text

Versioning • Clients don’t get updated • Typically handled by URL • Can also be specified in the header

Slide 55

Slide 55 text

Versioning routerV1 = routers.DefaultRouter() … ! urlpatterns = patterns('', url(r’^api/v1', include(routerV1.urls)), … )

Slide 56

Slide 56 text

Documentation

Slide 57

Slide 57 text

–Kenneth Reitz “Documentation is king.”

Slide 58

Slide 58 text

Documentation • Plan your API before you try to integrate • Write docs before you code • Docs inform the tests you write

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Testing

Slide 61

Slide 61 text

Testing • Your API is a promise to your fellow developers • Unit testing helps you keep your promises

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

Questions & Discussion

Slide 64

Slide 64 text

Thanks! Kevin Harvey @kevinharvey [email protected]

Slide 65

Slide 65 text

Further Reading • http://www.django-rest-framework.org/ • http://en.wikipedia.org/wiki/ Representational_state_transfer • http://jacobian.org/writing/rest-worst-practices/ • http://www.ics.uci.edu/~fielding/pubs/ dissertation/top.htm