← Homepage

DRF and Unique Field Based Bundling

Django Rest Framework is very powerful API framework with built-in class based views and serializers. Despite of having numerous features, DRF lets you to customize deeply. In this post, I would like to serialize items within json object instead of json array.

Here is the traditional listing response. There are list of book items which are compound in list.

[
    {
        "id": 1,
        "title": "The Man in the High Castle",
        "writer": "Philip K. Dick",
        "isbn": "9780679740674"
    },
    {
        "id": 2,
        "title": "B Is for Beer",
        "writer": "Tom Robbins",
        "isbn": "9780061687273"
    },
    {
        "id": 3,
        "title": "Tales of Nasrettin Hoca",
        "writer": "Aziz Nesin",
        "isbn": "9789759548100"
    }
]

However, API consumer sometimes would like to go over the response more easily. Imagine this scenario, Frontend would like to reach writer of the book immediately with it’s ISBN. In traditional way, FE will iterate over all list items and look it’s ISBN is correct or not. If so, It returns value of writer keyword in book object.

In this case, Bundle like below will be more useful for the API consumer. There are items bundled with-in their ISBN numbers.

{
    "9780679740674": {
        "id": 1,
        "title": "The Man in the High Castle",
        "writer": "Philip K. Dick"
    },
    "9780061687273": {
        "id": 2,
        "title": "B Is for Beer",
        "writer": "Tom Robbins"
    },
    "9789759548100": {
        "id": 3,
        "title": "Tales of Nasrettin Hoca",
        "writer": "Aziz Nesin"
    }
}

We can easily implement a mixin and use it with ModelSerializer. In the mixin, We just need to override to_representation() method of ModelSerializer and ListSerializer.

# books/mixins.py

from rest_framework.serializers import ListSerializer as DRFListSerializer


class UniqueFieldMixin(object):
    unique_lookup_field = 'id'

    def to_representation(self, instance):
        bundle = super().to_representation(instance)

        lookup_value = str(bundle.get(self.unique_lookup_field))
        bundle.pop(self.unique_lookup_field)

        return {lookup_value: bundle}


    class ListSerializer(DRFListSerializer):
        def to_representation(self, data):
            from collections import OrderedDict

            context = OrderedDict()

            for item in data:
                context.update(self.child.to_representation(item))

            return context

        @property
        def data(self):
            return super(DRFListSerializer, self).data

Set the field which will be key of the bundle in unique_lookup_field. Use the UniqueFieldMixin.ListSerializer as a list_serializer_class.

# books/serializers.py

from books.mixins import UniqueFieldMixin
from books.models import Book
from rest_framework.serializers import ModelSerializer


class BookSerializer(UniqueFieldMixin, ModelSerializer):
    unique_lookup_field = 'isbn'

    class Meta:
        model = Book
        fields = ('id', 'title', 'writer', 'isbn')
        list_serializer_class = UniqueFieldMixin.ListSerializer

That’s it!