Andrea Stagi
Develover @
Nephila
Open source Framework to build your own WEB API
github.com/tomchristie/django-rest-framework
The app
https://github.com/DjangoBeer
from django.db import models
from django.contrib.auth.models import User
class CoffeeUser(models.Model):
user = models.OneToOneField(User)
twitter = models.CharField(max_length=30, default='')
class Badge(models.Model):
title = models.CharField(max_length=100)
description = models.CharField(max_length=100)
class Consumption(models.Model):
user = models.ForeignKey(User, related_name='consumptions')
created = models.DateTimeField(auto_now_add=True)
class PoweredBadge(models.Model):
user = models.ForeignKey(User)
badge = models.ForeignKey(Badge)
power = models.PositiveIntegerField(default=1)
Serialization
from .models import Badge, Consumption, PoweredBadge
from django.contrib.auth.models import User
from rest_framework import serializers
class PoweredBadgeSerializer(serializers.ModelSerializer):
title = serializers.CharField(source='badge.title', read_only=True)
description = serializers.CharField(source='badge.description', read_only=True)
class Meta:
model = PoweredBadge
fields = ('id', 'title', 'description', 'power')
class BadgeSerializer(serializers.ModelSerializer):
class Meta:
model = Badge
fields = ('id', 'title', 'description')
# To be continued..
# ....
class ConsumptionSerializer(serializers.ModelSerializer):
user = serializers.IntegerField(source='user.pk', read_only=True)
class Meta:
model = Consumption
fields = ('id', 'user', 'created')
class UserSerializer(serializers.ModelSerializer):
badges = PoweredBadgeSerializer(source='poweredbadge_set', many=True)
twitter = serializers.CharField(source='coffeeuser.twitter')
class Meta:
model = User
fields = ('id', 'username', 'badges', 'twitter')
Custom Serializers
class StatisticSerializer(serializers.BaseSerializer):
def to_representation(self, obj):
user_data = {}
user_data['user'] = UserSerializer(obj).data
user_data['consumptions'] = [
ConsumptionSerializer(consumption).data for consumption in obj.consumptions.all()
]
return user_data
Views
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Badge
from .serializers import BadgeSerializer
class BadgeList(APIView):
"""
List all badges or create a new badge.
"""
def get(self, request, format=None):
badges = Badge.objects.all()
serializer = BadgeSerializer(badges, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = BadgeSerializer(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)
class BadgeDetail(APIView):
"""
Retrieve, update or delete a badge instance.
"""
def get_object(self, pk):
try:
return Badge.objects.get(pk=pk)
except Badge.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
badge = self.get_object(pk)
serializer = BadgeSerializer(badge)
return Response(serializer.data)
def put(self, request, pk, format=None):
badge = self.get_object(pk)
serializer = BadgeSerializer(badge, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def patch(self, request, pk, format=None):
badge = self.get_object(pk)
serializer = BadgeSerializer(badge, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
badge = self.get_object(pk)
badge.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from .views import BadgeDetail, BadgeList
urlpatterns = [
url(r'^badges/$', BadgeList.as_view()),
url(r'^badges/(?P<pk>[0-9]+)$', BadgeDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Authentication
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
Token Authentication
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
for user in User.objects.all():
Token.objects.get_or_create(user=user)
from rest_framework.authtoken import views
urlpatterns += [
url(r'^api-token-auth/', views.obtain_auth_token)
]
from rest_framework.authtoken import views
urlpatterns += [
url(r'^api-token-auth/', views.obtain_auth_token)
]
Authorization: Token 9944b09199c62bcf9
{ 'token' : '9944b09199c62bcf9' }
Django OAuth Toolkit
DRF OAUTH
JSON WEB TOKEN AUTH
MORE @ http://bit.ly/1yt2Ign
Custom authentication
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions
class DeviceAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
...
Check X_DEVICE header, return None if it's not set
Check if Device is allowed and return it
Otherwise raise AuthenticationFailed
permissions
from rest_framework import permissions
class BlacklistPermission(permissions.BasePermission):
"""
Global permission check for blacklisted IPs.
"""
def has_permission(self, request, view):
ip_addr = request.META['REMOTE_ADDR']
blacklisted = Blacklist.objects.filter(ip_addr=ip_addr).exists()
return not blacklisted
Object Permissions
.check_object_permissions(request, obj)
class BadgeDetail(APIView):
def get(self, request, pk, format=None):
badge = self.get_object(pk)
self.check_object_permissions(request, badge)
serializer = BadgeSerializer(badge)
return Response(serializer.data)
def put(self, request, pk, format=None):
badge = self.get_object(pk)
self.check_object_permissions(request, badge)
serializer = BadgeSerializer(
badge, data=request.data
)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(
serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
def patch(self, request, pk, format=None):
badge = self.get_object(pk)
self.check_object_permissions(request, badge)
serializer = BadgeSerializer(
badge,
data=request.data, partial=True
)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(
serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
#...
If you're writing your own views and want to enforce object level permissions, or if you override the get_object method on a generic view, then you'll need to explicitly call the
method
on the view at the point at which you've retrieved the object.
Custom permissions
from rest_framework import permissions
class IsSuperUserOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow superusers to edit objects.
"""
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
# You need to be superuser to do POST, PUT and PATCH requests.
return request.user.is_superuser
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 object.
return obj.user == request.user
Mixins
from rest_framework import mixins
from rest_framework import generics
class BadgeList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
"""
List all badges or create a new badge.
"""
queryset = Badge.objects.all()
serializer_class = BadgeSerializer
permission_classes = (IsSuperUserOrReadOnly,)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class BadgeDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
"""
Retrieve, update or delete a badge instance.
"""
queryset = Badge.objects.all()
serializer_class = BadgeSerializer
permission_classes = (IsSuperUserOrReadOnly,)
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
Generic Class Based Views
from rest_framework import permissions
from rest_framework import generics
class BadgeList(generics.ListCreateAPIView):
"""
List all badges or create a new badge.
"""
queryset = Badge.objects.all()
serializer_class = BadgeSerializer
permission_classes = (IsSuperUserOrReadOnly,)
class BadgeDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Retrieve, update or delete a badge instance.
"""
queryset = Badge.objects.all()
serializer_class = BadgeSerializer
permission_classes = (IsSuperUserOrReadOnly,)
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
rest_framework/generics.py
ViewSets
from rest_framework import viewsets
from rest_framework.decorators import list_route
from rest_framework.response import Response
class BadgeViewSet(viewsets.ModelViewSet):
queryset = Badge.objects.all()
serializer_class = BadgeSerializer
permission_classes = (IsSuperUserOrReadOnly,)
class ConsumptionViewSet(viewsets.ModelViewSet):
serializer_class = ConsumptionSerializer
permission_classes = (permissions.IsAuthenticated,)
@list_route(methods=['get'], permission_classes=[permissions.AllowAny])
def all(self, request):
consumptions = Consumption.objects.all()
serializer = ConsumptionSerializer(consumptions, many=True)
return Response(serializer.data)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def get_queryset(self):
return Consumption.objects.filter(user=self.request.user)
DRF in the real life
@Nephila
Pagination
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 100
}
HTTP 200 OK
{
"count": 1023
"next": "https://localhost:8000/pubs/?page=4",
"previous": "https://localhost:8000/pubs/?page=2",
"results": [
…
]
}
GET https://localhost:8000/pubs/?page=3
@Taiga.io
https://github.com/taigaio/taiga-back
class ConditionalPaginationMixin(object):
def get_paginate_by(self, *args, **kwargs):
if "HTTP_X_DISABLE_PAGINATION" in self.request.META:
return None
return super().get_paginate_by(*args, **kwargs)
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
pass
class ModelCrudViewSet(pagination.HeadersPaginationMixin,
pagination.ConditionalPaginationMixin,
ModelViewSet):
pass
class ProjectViewSet(ModelCrudViewSet):
serializer_class = serializers.ProjectDetailSerializer
...
@list_route(methods=["GET"])
def by_slug(self, request):
slug = request.QUERY_PARAMS.get("slug", None)
project = get_object_or_404(models.Project, slug=slug)
return self.retrieve(request, pk=project.pk)
@detail_route(methods=["GET"])
def stats(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "stats", project)
return response.Ok(services.get_stats_for_project(project))
@detail_route(methods=["POST"])
def star(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "star", project)
votes_service.add_vote(project, user=request.user)
return response.Ok()
@detail_route(methods=["POST"])
def unstar(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "unstar", project)
votes_service.remove_vote(project, user=request.user)
return response.Ok()
SWAPI
https://github.com/phalt/swapi
Pokèapi
https://github.com/phalt/pokeapi
Thank you!
github.com/astagi
Twitter: @4stagi
STAGI.ANDREA@GMAIL.COM
DjangoBeer Django REST Framework
By Andrea Stagi
DjangoBeer Django REST Framework
- 2,332