Nested Serializers in Django Rest Framework

Vinaykumarmaurya
Dev Genius
Published in
6 min readMar 7, 2021

--

Create Nested serializer’s API using DRF which supports GET, POST, PUT methods.

We know today almost all websites and mobile apps are based on APIs. We play songs on Spotify, it instantly played, due to API . Searching temperatures in your region, you get results within a second. This is because of Api's interaction with apps. We can create our API in different technologies. DRF is one of them.

In this post, I’m going to show you how to create an API in the Django rest framework as well as how to deal with nested serializers. Why I have mentioned nested serializer, cause if you go to find out about this, hardly you will find the exact answers. For your ease, I’m touching on this scenario. Let’s Start!

For creating rest API in Django make sure you have installed Django and Django rest framework in your virtual environment. For installation, you can visit Django and Django rest framework websites. Once installation is done create your project and app. In my case, my Project name is DProject and my app name is API .

In DProject/urls.py file

from django.contrib import admin
from django.urls import path,include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('api.urls')),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

The static setting is for serving files in your app.

In DProject/setting.py make sure these settings should be there.

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'api',
]
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

Now coming to the API model.

from django.db import models


class Profile(models.Model):
display_name = models.CharField(max_length=30)
mobile = models.IntegerField()
address = models.TextField()
email = models.EmailField()
dob = models.DateField()
photo=models.FileField(upload_to='profile/',null=True,blank=True)
status = models.IntegerField(default=1)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)


class Hobby(models.Model): user=models.ForeignKey(Profile,on_delete=models.CASCADE,related_name='user_hobby',null=True,blank=True)
name = models.CharField(max_length=30)
description = models.TextField()
status = models.IntegerField(default=1)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

Profile model is for collecting basic details of users and Hobby model is for collecting hobbies of users having a profile.

Now I’m going to make Profile API in such a way that I can save hobbies of users with the same API. Here I’m going to use nested serializers. The serializers in the REST framework work very similarly to Django’s Form and ModelForm classes. DRF provides a Serializer class that gives you a powerful, generic way to control the output of your responses, as well as a ModelSerializer class that provides a useful shortcut for creating serializers that deal with model instances and querysets. I’m going to use ModelSerializer .

In the api folder , I’m creating a file serializer.py .

from rest_framework import serializers
from .models import Profile,Hobby


class HobbySerializer(serializers.ModelSerializer):

class Meta:
model = Hobby
fields = '__all__'
class ProfileSerializer(serializers.ModelSerializer):
user_hobby = HobbySerializer(many=True)

class Meta:
model = Profile
fields = '__all__'

def create(self, validated_data):
user_hobby = validated_data.pop('user_hobby')
profile_instance = Profile.objects.create(**validated_data)
for hobby in user_hobby:
Hobby.objects.create(user=profile_instance,**hobby)
return profile_instance

Did you notice, I’m calling my HobbySerializer class in the ProfileSerializer. This is called nesting of serializers . To handle the POST method I will override the create method in which I’m saving profile data and then saving the hobby of the person whose profile was created. Now let’s call our ProfileSerializer in the view.

In api/views.py

from rest_framework import viewsets
from .models import Profile
from .serializers import ProfileSerializer
# Create your views here.


class ProfileViewset(viewsets.ModelViewSet):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
http_method_names = ['get','post','retrieve','put','patch']

Now I’m creating urls.py file inside API folder for calling ProfileViewset .

In api/urls.py

from django.urls import path,include
from api.views import ProfileViewset
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('profile',ProfileViewset)

urlpatterns = [
path('', include(router.urls)),
]

Let’s hit the API in Postman.

post data

Bingo!! Did you notice, profile data was saved successfully along with hobby data? We can post multiple hobbies by sending a list of hobbies in JSON format, cause while calling HobbySerializer class inside ProfileSerializer , i made many=True attribute is enabled. which accepts multiple objects at the same time.

Now time to focus upon the PUT method. I want the same format of data to be edited. For that, I will send an id of the profile along with the id of hobby which I want to edit. Consider the situation I’m going to deal with.

Right now I have the below data in my DB .

{"id": 4,"user_hobby": [{"id": 3,"name": "Movie","description": "Love to watch movie","status": 1,"created_at": "2021-03-07T04:19:12.154278Z","updated_at": "2021-03-07T04:19:12.154515Z","user": 4},{"id": 4,"name": "Songs","description": "Love to listen Party song","status": 1,"created_at": "2021-03-07T04:19:12.266035Z","updated_at": "2021-03-07T04:19:12.266291Z","user": 4}],"display_name": "Test User","mobile": 1234567890,"address": "Tester Address","email": "tester@gmail.com","dob": "2020-11-16","photo": null,"status": 1,"created_at": "2021-03-07T04:19:12.015695Z","updated_at": "2021-03-07T04:19:12.015750Z"}

I want to edit my hobby with id=4 and add a new hobby instead hobby with id=3 . For that will send id=4 modified data and a new hobby data without any id , this way my new hobby will replace the hobby with id=3 .

Let’s override the update method in ProfileSerializer .

In api/serializer.py

def update(self, instance, validated_data):
user_hobby_list = validated_data.pop('user_hobby')
instance.display_name = validated_data.get('display_name', instance.display_name)
instance.mobile = validated_data.get('mobile', instance.mobile)
instance.address = validated_data.get('address', instance.address)
instance.dob = validated_data.get('dob', instance.dob)
instance.email = validated_data.get('email', instance.email)
instance.photo = validated_data.get('photo', instance.photo)
instance.save()
"""getting list of hobbies id with same profile instance""" hobbies_with_same_profile_instance = Hobby.objects.filter(user=instance.pk).values_list('id', flat=True)

hobbies_id_pool = []

for hobby in user_hobby_list:
if "id" in hobby.keys():
if Hobby.objects.filter(id=hobby['id']).exists():
hobby_instance = Hobby.objects.get(id=hobby['id'])
hobby_instance.name = hobby.get('name', hobby_instance.name)
hobby_instance.description = hobby.get('description',hobby_instance.description)
hobby_instance.save()
hobbies_id_pool.append(hobby_instance.id)
else:
continue
else:
hobbies_instance = Hobby.objects.create(user=instance, **hobby)
hobbies_id_pool.append(hobbies_instance.id)

for hobby_id in hobbies_with_same_profile_instance:
if hobby_id not in hobbies_id_pool:
Hobby.objects.filter(pk=hobby_id).delete()

return instance

In the 1st line of the update method, I’m popping hobby’s data then updating existing profile data. Further, I’m fetching a list of hobbies id with the same profile instance. hobbies_id_pool list is for maintaining ids of hobbies which I’m going to edit or create. Then iterating user_hobby_list and checking if a hobby object has an id or not. In case a hobby has id then I will update the hobby with that id otherwise I will create a new hobby . At last, I’m checking ids of hobbies_with_same_profile_instance list in hobbies_id_pool list. if id is not in the hobbies_id_pool list then will delete that hobby. Let’s test this logic.

Update data

Voila !! did you notice, i added new hobby ‘Riding’ in the same profile of id=4 and edited hobby with id=4 . I edited the display name as well . hobby with id=3 is deleted . All working fine . This is the full code of serializers.py file .

from rest_framework import serializers
from .models import Profile,Hobby


class HobbySerializer(serializers.ModelSerializer):

class Meta:
model = Hobby
fields = '__all__'


class ProfileSerializer(serializers.ModelSerializer):
user_hobby = HobbySerializer(many=True)

class Meta:
model = Profile
fields = '__all__'

def create(self, validated_data):
user_hobby = validated_data.pop('user_hobby')
profile_instance = Profile.objects.create(**validated_data)
for hobby in user_hobby:
Hobby.objects.create(user=profile_instance,**hobby)
return profile_instance

def update(self, instance, validated_data):
user_hobby_list = validated_data.pop('user_hobby')
instance.display_name = validated_data.get('display_name', instance.display_name)
instance.mobile = validated_data.get('mobile', instance.mobile)
instance.address = validated_data.get('address', instance.address)
instance.dob = validated_data.get('dob', instance.dob)
instance.email = validated_data.get('email', instance.email)
instance.photo = validated_data.get('photo', instance.photo)
instance.save()

hobbies_with_same_profile_instance = Hobby.objects.filter(user=instance.pk).values_list('id', flat=True)

hobbies_id_pool = []

for hobby in user_hobby_list:
if "id" in hobby.keys():
if Hobby.objects.filter(id=hobby['id']).exists():
hobby_instance = Hobby.objects.get(id=hobby['id'])
hobby_instance.name = hobby.get('name', hobby_instance.name)
hobby_instance.description = hobby.get('description',hobby_instance.description)
hobby_instance.save()
hobbies_id_pool.append(hobby_instance.id)
else:
continue
else:
hobbies_instance = Hobby.objects.create(user=instance, **hobby)
hobbies_id_pool.append(hobbies_instance.id)

for hobby_id in hobbies_with_same_profile_instance:
if hobby_id not in hobbies_id_pool:
Hobby.objects.filter(pk=hobby_id).delete()

return instance

All that was very simple and understanding. For more detail, you can visit Django rest framework’s official documentation page.

Follow me for more upcoming posts and thanks if my post helped you !!

--

--