Need some help on your next project? Drop me an email: matt@mcollins.co.uk
Matt CollinsWeb Developer

Django Rest Framework Serializers for frontend components

All Notes

Posted on: 06/06/2017

Following the launch of Wilderness Now I wanted to talk about the approach Pete Goodman and I took integrating a component based frontend with Django. We've put together an example project on Github that shows all of it in practice.

Each component in our library is a distinct part of a layout that is encapsulated into it's own module. A component's HTML is written using Django's templating and each component is included into a page template and passed data to customise it. For example:

{% include 'components/article/ArticleThumb/ArticleThumb.html' with data=article %}

Mapping Django models to components

A component is often a representation of data held within a model or a group of models within our Django application. However the component may only need a subset of the model's data and potentially data that is generated dynamically (e.g. an absolute url).

So to map a model to the data expected by a component we used serializers from Django Rest Framework (DRF). This may seem odd at first, as DRF serializers were designed to work with a REST API and we're not using one here. But if you look at what a serializer does, they fit perfectly for the job of mapping models to components.

More on: Django Rest Framework Serializers

Using serializers also help us achieve our Don't Repeat Yourself (DRY) principles as it means we can create one serializer per model/component combination and reuse them across different Django views.

Here's an example serializer that maps an Article model to a component called ArticleThumb:

from rest_framework.serializers import (
    CharField,
    SerializerMethodField,
    ModelSerializer,
    URLField,
)
from .models import Article


class ArticleArticleThumbSerializer(ModelSerializer):
    """
    Maps Article model to ArticleThumb component
    """
    title = CharField(source='name')
    url = URLField(source='get_absolute_url')

    class Meta:
        model = Article
        fields = ('title', 'url', )

Within our Django view we can then simply load the data from the DB, serialize and pass to the context like so:

from django.views.generic import TemplateView

from .models import Article
from .serializers import ArticleArticleThumbSerializer


class ArticlesView(TemplateView):
    template_name = 'articles.html'

    def get_context_data(self, **kwargs):
        context = super(ArticlesView, self).get_context_data(**kwargs)
        articles = Article.objects.all()
        articles_serializer = ArticleArticleThumbSerializer(
            articles, many=True
        )
        context['articles'] = articles_serializer.data
        return context

Our view context now has the data in the exact structure our ArticleThumb component expects and we just need to loop over articles to generate the HTML we need:

{% for article in articles %}
    {% include 'components/article/ArticleThumb/ArticleThumb.html' with data=article %}
{% endfor %}

See a full example Django application on Github: https://github.com/madebycomrades/django-component-library/tree/master/dcl/articles

An abstract component serializer

By creating an abstract component seralizer we can also centralise functionality that appears across all component serializers. We found a lot of our serializers need to resize an image for a thumbnail so we added a generic approach for that:

from easy_thumbnails.exceptions import InvalidImageFormatError
from easy_thumbnails.files import get_thumbnailer
from rest_framework.serializers import (
    ModelSerializer,
    SerializerMethodField,
)


class ComponentSerializer(ModelSerializer):
    """
    Abstract Serializer class for mapping models to frontend components

    Naming:
        <model name><component name>Serializer
        e.g. CategoryActivitiesCategorySerializer

    Always override Meta and include model property
    """
    src = SerializerMethodField()

    class Meta:
        thumbnail_size = (800, 450)
        thumbnail_source = 'image'

    def get_src(self, obj):
        image_field = getattr(obj, self.Meta.thumbnail_source)
        if image_field:
            thumbnailer = get_thumbnailer(image_field)
            thumbnail_options = {
                'size': self.Meta.thumbnail_size
            }

            if (
                hasattr(self.Meta, 'thumbnail_crop') and
                self.Meta.thumbnail_crop
            ):
                thumbnail_options['crop'] = True

            try:
                return thumbnailer.get_thumbnail(thumbnail_options).url
            except InvalidImageFormatError:
                return ''
        else:
            return ''

Each serializer that needs to use this can then just specify src in its fields and customize the thumbnail properties.