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 %}
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
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.