DRF serializers.Serializer: Saving Many-to-Many Relationships Made Simple

Aditya Anand
7 min readJul 26, 2023

--

Django Rest Framework: A powerful Web Browsable API platform, extending Django’s greatness.

Please find the code on my Github repo : Here
Social media : Aditya
Recommended : Implementing Token-Based Authentication

Photo by Mitul Grover on Unsplash

How Does serializers.Serializer Simplify Data Handling?

Handling many-to-many relationships in Django Rest Framework (DRF) can be a powerful and versatile tool for building complex APIs. However, it can also become a bit perplexing, especially when it comes to data serialization. That’s where DRF’s serializers.Serializer comes to the rescue. With its flexibility and ease of use, serializers.Serializer simplifies the process of dealing with many-to-many relationships, allowing developers to focus on building robust APIs without getting lost in the complexities of data management. In this article, we will delve into the world of many-to-many field serialization using serializers.Serializer, and explore practical examples that demonstrate just how straightforward and efficient it can be to save many-to-many relationships with DRF.

To understand things better in this blog we’ll be taking help of example of Movie and Genre(genre refers to the categorization or classification of films based on their common themes, styles, and subject matters. Some common movie genres : Action, Comedy, Drama, Romance etc.) Relationship.
Little over view is Movie will have ManyToMany relationship with Genre as a movie can belong to multiple genres.

Before starting I am assuming you have little familiarity with the concept of Django like Models,Views and with DRF like Serializer. If not still this blog will help to understand how to perform CRUD operation using DRF as we will we taking help of an example.

Let’s dive deep into creating API which can do CRUD.For simplification I’ll be using rest_framewok.views.APIView. Wanted to read more DRF

Let’s create our project and apps within it.

django-admin startproject CRUD_ManyToMany #To create a Project
cd CRUD_ManyToMany #change Directory to CRUD_ManyToMany and create a app
python manage.py startapp api #To create an app
My Folder Structure

Register your app in CRUD_ManyToMany/settings.py

CRUD_ManyToMany/settings.py

Also add url in url CRUD_ManyToMany/urls.py

CRUD_ManyToMany/urls.py

Now It’s time to work with our api.

In api/models.py

class Genre(models.Model):
genre = models.CharField(max_length=100)

def __str__(self):
return self.genre


class Movie(models.Model):
name = models.CharField(max_length=500)
genre = models.ManyToManyField(Genre)

def __str__(self):
return self.name

Register Model in admin.py so that we can add sample data later.From admin page.

In api/admin.py

from django.contrib import admin
from .models import Movie,Genre

admin.site.register(Movie)
admin.site.register(Genre)

In api/serializers.py

In api/serializers.p

In api/views.py

In api/views.p

Now time to makemigrations and migrate it.Also create and super user after migrating. So that to we can use Django great feature which is Admin pannel.

python manage.py makemigrations
python manage.py migrate

Migrate output:

After migration successfully executed.
python manage.py createsuper user 
#remember username and password while creating super user.

As we have registered our model in api/admin.py. Now we can access it and admin pannel and enter some data manually.

let’s run the server and hit the url

http://127.0.0.1:8000/admin

Enter you username and pass which was given at the time of creating super user.

Let’s create some Genre and Movie quickly.

Click on Genre < ADD GENRE (on top right) . I have added some genres in Genre Table.

Genre sample data.

Repeat same process for Movie Table and for genre choose multiple from the drop down list.

I have added some movies.

Movie sample data.
A sample data for movie.

As we have data we are good to go !!

Let’s the url http://localhost:8000/api/

If you have followed everything correctly.You’ll get this output (I am using browser to call the API).

API Response, GET/Read(cRud)
[
{
"name": "Super Man",
"genre": [
{
"genre": "Adventure"
},
{
"genre": "Action"
}
]
},
{
"name": "Dumb and Dumber",
"genre": [
{
"genre": "Comedy"
},
{
"genre": "Action"
}
]
},
{
"name": "Titanic",
"genre": [
{
"genre": "Love"
},
{
"genre": "Youth"
},
{
"genre": "Old is gold"
}
]
}
]

With this I congratulate to you guys 👏. You have successfully created an API which servers GET request.
It was really fun 😺.

In the Journey of creating CRUD with ManyToMany field we have completed 25% of our journey. Rest 75% will be really cake walk and very easy to implement. Stay with your pilot Aditya Anand 🤞

Now it’s time to modify our serializer , views to accept POST(create) request.

In api/views.py(add post method)

class MovieAPIView(APIView):
def get(self,request):
queryset = Movie.objects.all()
serializer = MovieSerializer(queryset,many=True)
return Response(serializer.data)

def post(self,request): #Crud
serializer = MovieSerializer(data = request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)

In api/serializers.py.

If we don’t add the create method it will throw error. `create()` method must be implemented.

error without create method.

At DRF docs

If you’re supporting writable nested representations you’ll need to write .create() or .update() methods that handle saving multiple objects.

Let’s add create method.

from .models import Genre
from .models import Movie
class MovieSerializer(serializers.Serializer):
name = serializers.CharField()
genre = GenreSerializer(read_only=True,many=True)

def create(self,validated_data): #create method
genres = self.initial_data['genre']

genresInstances = []

for genre in genres:
genresInstances.append(Genre.objects.get(pk = genre['id']))
movie = Movie.objects.create(**validated_data)
movie.genre.set(genresInstances)
return movie

Little explanation about the code.

first taking genres out of initial_data.

Why initial data?
Because as we have given read_only = True in
genre = GenreSerializer(read_only=True,many=True) , serializer will give validated data without genre, to access genre which is coming from some frontend application we are taking it out from intial data.
read_only is True because otherwise MovieSerializer will assume that we are also updating genre which we are not , we just wanted to map our Movie new record which we are going to create.

After updating views.py and serializer.py we good to hit our url again and make post request.

For that I’ll be using this sample data.

{
"name": "BatMan",
"genre": [
{
"id": 1
},
{
"id": 2
}
]
}
Making POST request.

Output :

Ouput of POST request.

With this congratulate you again. Now we have successfully made POST request. As out API responded with HTTP 200 OK.

You are really doing good.Now our 50% work is done.The rest 50% is really very simple. Stay connected!

So far we have seen how to get data, save new data.
Now lets add functionality to get the specific record/data, update it and also delete it.
For this we need to modify our api/urls.py, api/views.py, api/serializers.py

In api/urls.py,

from django.urls import path
from .views import MovieAPIView,MovieDetailAPI
urlpatterns = [
path('',MovieAPIView.as_view(),name="movie"),
path('<int:pk>/',MovieDetailAPI.as_view(),name="movie_detail") # path added
]

In api/views.py (Added new API View)

from rest_framework.exceptions import ValidationError
class MovieDetailAPI(APIView): #added new API View
def get_object(self,pk):
try:
return Movie.objects.get(pk=pk)
except:
raise ValidationError({'msg':'Movie Doesnot exist'})

def get(self,request,pk):
movie = self.get_object(pk)
serializer = MovieSerializer(movie)
return Response(serializer.data)

def put(self,request,pk):
movie = self.get_object(pk=pk)
serializer = MovieSerializer(movie,data = request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)

def delete(self,request,pk):
movie = self.get_object(pk=pk)
movie.delete()
return Response({"msg":"Movie Deleted"})

Here method get_object will return us the current record according to ‘pk’ passed in url.
After that method get will be serving read request for a record similary method put is for serving update request and method delete will be serving the delete request.

In api/serializers.py (Added Update method)

class MovieSerializer(serializers.Serializer):
name = serializers.CharField()
genre = GenreSerializer(read_only=True,many=True)

def create(self,validated_data): #create method
genres = self.initial_data['genre']

genresInstances = []

for genre in genres:
genresInstances.append(Genre.objects.get(pk = genre['id']))
movie = Movie.objects.create(**validated_data)
movie.genre.set(genresInstances)
return movie

def update(self,instance,validated_data):
try: # handling if not getting genre from frontend client
genres = self.initial_data['genre']
genresInstances = []
for genre in genres:
genresInstances.append(Genre.objects.get(pk = genre['id']))
instance.genre.set(genresInstances) # set function is given by django model
except:
pass
for k, v in validated_data.items():
setattr(instance, k, v)
instance.save()
return instance

With this our get,update and delete is done !! ooofffuuuuuuuu Finally now its time to test it out Recipe which we build with a lot of work.

Let’s hit our url again
url : http://localhost:8000/api/1/
‘1’ is for the movie ID 1.

A Record.

Let’s update it with this data

{
"name": "Super Man : The Hero",
"genre": [
{
"id":1
}
]
}

Oh Great ! It has updated our record.

Now It’s time to test the delete functionality.
Clicking on the delete button will ask me for confirmation.I’ll click on Delete again to test whether it gets deleted or not!

Oh great! It has deleted our record.

With this , our REST API is completed with CRUD functionality by using ManyToMany Field in api/model.py.
Thanks for reading. I hope i have added some value then please applaud (click on this icon 👏) it will really motivate me.

Little about me:
I am Aditya Anand. A full-stack developer who loves to learn new things and share knowledge.
Github : Aditya Anand

--

--

Aditya Anand

A FullStack Developer.Working with tech like Python,React,Vue. Also sharing what I have and learning what I can.