DEV Community

CH S Sankalp jonna
CH S Sankalp jonna

Posted on • Originally published at sankalpjonna.com

Representing foreign key values in Django serializers

The primary purpose of a serializer is to convert a Django model or rather a database table into a format that can be transferred over the internet such as a JSON output or an XML output.

This becomes slightly tricky when you have two models that are related to each other through a foreign key or a many to many relationship. 

Serializing a model that contains a foreign key to another model can be done in various ways depending on what you plan to do with this data. Most widely used method is to use a nested serializer.

Using a nested serializer

Consider the relationship between a song and an artist. There could be multiple songs each of which may belong to the same artist thus requiring the need for a foreign key.

from django.db import models

class Artist(models.Model):
  name = models.CharField(max_length=255)

  def __str__(self):
    return self.name

class Song(models.Model):
  name = models.CharField(max_length=255)
  artist = models.ForeignKey(Artist, related_name="songs", on_delete=models.CASCADE)

  def __str__(self):
    return self.name
Enter fullscreen mode Exit fullscreen mode

Let’s say that we want to present the Artist model in a way that it includes all the songs recorded by that artist. This can be achieved using a nested serializer.

from rest_framework import serializers
from .models import Song, Artist


class SongSerializer(serializers.ModelSerializer):
    class Meta:
      model = Song
      fields = ('id', 'name', 'artist')

class ArtistSerializer(serialisers.ModelSerializer):
  songs = SongSerializer(many=True)

  class Meta:
    model = Artist
    fields = ('id', 'name', 'songs')
Enter fullscreen mode Exit fullscreen mode

Upon using the ArtistSerializer, the response that is returned will look like this

{
  "id": 1,
  "name": "Beatles",
  "songs": [
    {
      "id": 1,
      "name": "I am the walrus"
    },
    {
      "id": 2,
      "name": "Come together"
    }
   ]
}
Enter fullscreen mode Exit fullscreen mode

By default, a nested serializer is read only. To use this serializer for validating an input and write data into the DB, one must explicitly override the create() or update() methods as shown below:

from rest_framework import serializers
from .models import Song, Artist


class SongSerializer(serializers.ModelSerializer):
    class Meta:
      model = Song
      fields = ('id', 'name', 'artist', 'duration_in_seconds')

class ArtistSerializer(serialisers.ModelSerializer):
  songs = SongSerializer(many=True)

  def create(self, validated_data):
    songs_data = validated_date.pop("songs")
    artist = Artist.objects.create(**validated_data)
    for song_data in songs_data:
      Song.objects.create(artist=artist, song=**song_data)
    return artist

  class Meta:
    model = Artist
    fields = ('id', 'name', 'songs')
Enter fullscreen mode Exit fullscreen mode

Using default serializer relations

Writing nested serializers can usually be avoided by using the various types of serializer relations provided by the Django REST framework that you can find here

In this tutorial, we will be covering PrimaryKeyRelatedField and SlugRelatedField. The rest of the serializer relation types are pretty similar and can be used interchangeably with these depending upon your use case.

In each of these types, you can create read only serializers by setting read_only=True or you can also use these to write data by validating inputs.

If you wish to make them writable, you simply have to include a queryset parameter in the serializer along with read_only=False  which will be used to validate the inputs. 

Let’s take a look at the most commonly used serializer relation to explain this further

PrimaryKeyRelatedField

If you already have the individual objects of the related fields, you can only serialize the primary key in order to identify the objects.

from rest_framework import serializers
from .models import Song, Artist

class ArtistSerializer(serialisers.ModelSerializer):
  songs = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

  class Meta:
    model = Artist
    fields = ('id', 'name', 'songs')
Enter fullscreen mode Exit fullscreen mode

This would get serialized as follows:

{
  "id": 1,
  "name": "Beatles",
  "songs": [
    1,
    2
   ]
}
Enter fullscreen mode Exit fullscreen mode

To make this serializer writable, use the following arguments:

from rest_framework import serializers
from .models import Song, Artist

class ArtistSerializer(serialisers.ModelSerializer):
  songs = serializers.PrimaryKeyRelatedField(
    many=True, 
    read_only=False,
    queryset=Song.objects.all()
  )

  class Meta:
    model = Artist
    fields = ('id', 'name', 'songs')
Enter fullscreen mode Exit fullscreen mode

SlugRelatedField

If the target of the relationship needs to be represented using a field other than the primary key, you can specify the field you would like to use.

from rest_framework import serializers
from .models import Song, Artist

class ArtistSerializer(serialisers.ModelSerializer):
  songs = serializers.SlugRelatedField(
    many=True, 
    read_only=True,
    slug_field="name"
  )

  class Meta:
    model = Artist
    fields = ('id', 'name', 'songs')
Enter fullscreen mode Exit fullscreen mode

This would get serialized as follows:

{
  "id": 1,
  "name": "Beatles",
  "songs": [
    "I am the walrus",
    "Come together"
   ]
}
Enter fullscreen mode Exit fullscreen mode

Closing notes

If you need complete control over how objects and relationships are created/represented at the time of serializing data or if you need to display the entire object at the time of serialization, using nested serializers is a better approach.

However if you are building an application where you already have access to the related models and you only need an identifier to reference them, I would recommend using one of the inbuilt serializer relations.

The above methods of handling foreign key relations in serializers are not just for ForeignKey fields. They also apply for ManyToManyField and OneToOneField.

Top comments (0)