Implementing HATEOAS in Django REST framework
Asked Answered
P

2

12

I'm trying to implement REST API that implements HATEOAS using Django REST Framework (DRF). I know that DRF itself doesn't support HATEOAS and I didn't find any examples of such implementation. Therefore I'm not sure on which level of DRF (Serializers / Views / Renderers) should I implement this functionality. Do you have some experiences, thoughts, insights or examples which could help me to start? Thank you.

Pollywog answered 26/11, 2015 at 17:11 Comment(0)
P
12

Here is my solution implemented on level of Viewset:

from rest_framework import viewsets
from rest_framework import generics
from serializers import EventSerializer, BandSerializer
from rest_framework.response import Response
from rest_framework import status

from collections import OrderedDict

class LinksAwarePageNumberPagination(PageNumberPagination):
   def get_paginated_response(self, data, links=[]):
       return Response(OrderedDict([
          ('count', self.page.paginator.count),
          ('next', self.get_next_link()),
          ('previous', self.get_previous_link()),
          ('results', data),
          ('_links', links),
       ]))

class HateoasModelViewSet(viewsets.ModelViewSet):
    """
    This class should be inherited by viewsets that wants to provide hateoas links
    You should override following methodes:
      - get_list_links
      - get_retrieve_links
      - get_create_links
      - get_update_links
      - get_destroy_links
    """

    pagination_class = LinksAwarePageNumberPagination


    def get_list_links(self, request):
        return {}

    def get_retrieve_links(self, request, instance):
        return {}

    def get_create_links(self, request):
        return {}

    def get_update_links(self, request, instance):
        return {}

    def get_destroy_links(self, request, instance):
        return {}

    def get_paginated_response(self, data, links=None):
        assert self.paginator is not None
        return self.paginator.get_paginated_response(data, links)

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data, links=self.get_list_links())

        serializer = self.get_serializer(queryset, many=True)

        return Response(OrderedDict([
            ('results', serializer.data),
            ('_links', self.get_list_links(request))
        ]))

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        data = serializer.data
        data['_links'] = self.get_retrieve_links(request, instance)
        return Response(data)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        data = serializer.data
        data['_links'] = self.get_create_links(request)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        data = serializer.data
        data['_links'] = self.get_update_links(request, instance)
        return Response(serializer.data)

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        data = {'_links': self.get_destroy_links(request, instance)}
        self.perform_destroy(instance)
        return Response(data, status=status.HTTP_204_NO_CONTENT)

Example of usage:

class EventViewSet(HateoasModelViewSet):
    queryset = Event.objects.all()
    serializer_class = EventSerializer

    def get_list_links(self, request):
        return {
            'self': {'href': request.build_absolute_uri(request.path)},
            'related_link1': {'href': '...'},
        }
Pollywog answered 28/11, 2015 at 2:12 Comment(0)
S
2

I know it is a relevant old thread, but for those who came to this now, there is a library django-rest-framework-json-api, which uses the json:api specification. This specification is completely compatible with the HATEOAS implementation.

I am also quite new to this; here is a tutorial that I used as a guide by the Columbia university which I found really helpful.

On the plus side I find it is compatible with api documentation using swagger, using the drf-yasg and drf-yasg-json-api libraries.

Spaceport answered 14/4, 2022 at 11:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.