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,209