Update Nested Serializers Fields with Django REST Framework
A tutorial on updating a foreign key-related object (many-to-one relationship) using a single PATCH request.
Table of contents
Introduction
Hello all ๐ ๐ !
If you have attempted to build a web API endpoint for CRUDing (creating, retrieving, updating and deleting) data objects in a database using Django REST framework, you will realise that the object fields related to other objects in the database are not CRUDed by default ๐ฐ . They have to be serialized appropriately ๐ช.
In this tutorial, we will build an employee data app that stores information (employee id, first name, last name, employment date and certifications) on every employee on the app as an object in the database. Since every employee, may possess one or more certifications (or even no certifications) we will use a Many-to-One relationship (also known as a foreign key relationship) to map every employee to their certifications in the database.
๐ See the full code of GitHub
Learning Objectives
- Create Django database models with a foreign-key relationship
- Create API end-points for CRUDing data in the database
- Test API end-points on Postman
Prerequisites ๐ฅ ๐ฅ
This demo assumes that you are familiar with the following;
- Setting up a Django app and deploying it locally. Visit the Django docs for a detailed tutorial on the process.
- Using a code editor of your choice to open and edit code files. I am using Pycharm.
- Running Django CLI commands from the relevant project directories to perform database migrations.
- Setting up a virtual environment on local machine to install Django and Django REST framework used for the tutorial.
Development ๐ป
Step 1: Building Django models and viewing on the Django admin site
The data fields that are stored for each employee are shown in the models.py file for the app below
import uuid
from django.utils.translation import gettext_lazy as _
from django.db import models
class Employees(models.Model):
"""
Model representing Employees
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
first_name = models.CharField(_('first_name'), max_length=255, blank=True, null=True)
last_name = models.CharField(max_length=255, blank=True, null=True)
employment_date = models.DateField(blank=True, null=True)
class Meta:
verbose_name_plural = "Employees"
ordering = ['first_name']
def __str__(self):
return self.first_name
class Certifications(models.Model):
"""
Model representing Employees
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(_('name'), max_length=255)
expiry_date = models.DateField(blank=True, null=True)
# relations
employees = models.ForeignKey(Employees, on_delete=models.SET_NULL, blank=True, null=True,
related_name='certifications')
class Meta:
verbose_name_plural = "Certifications"
ordering = ['name']
def __str__(self):
return self.name
Run the following commands on your terminal to implement the changes in the database.
python manage.py makemigrations
python manage.py migrate
Create a superuser account that you can use to access the admin site using the command below.
python manage.py createsuperuser
Register your models on the admin.py file so that the tables that are created for them in the database are visible to you on the admin site.
from django.contrib import admin
from . import models
@admin.register(models.Certifications)
class CertificationsAdmin(admin.ModelAdmin):
list_display = ["id", "name", "expiry_date"]
@admin.register(models.Employees)
class EmployeesAdmin(admin.ModelAdmin):
list_display = ["id", "first_name", "last_name", "employment_date"]
Open the Django admin site for your app to view the database models ๐ ๐ ๐.
Step 2: Developing API endpoints to CRUD the app data using Django REST Framework
- Ensure that Django REST framework is included in the settings.py file.
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'employee', 'rest_framework' ]
- Update your urls.py file with the relevant CRUD endpoint views for your models.
from django.contrib import admin
from django.urls import path
from employee.api import (
CertificationsListView,
EmployeesListCreateView,
EmployeesRetrieveUpdateDeleteView
)
urlpatterns = [
path('admin/', admin.site.urls),
# Certifications
path("certifications", CertificationsListView.as_view(), name="certifications"),
# Employees
path("", EmployeesListCreateView.as_view(), name="employees"),
path("<uuid:pk>", EmployeesRetrieveUpdateDeleteView.as_view(), name="employees-details"),
]
- Django REST framework uses generic views to enable users to CRUD endpoints quite easily. Further info about using them can be found on the DRF generic views docs. Create an api.py file in your app folder and update it with the following DRF class based views for the app.
from rest_framework import generics
from .models import (
Certifications,
Employees
)
from .serializers import (
CertificationsSerializer,
EmployeesSerializer,
)
class CertificationsListView(generics.ListAPIView):
"""
List all Certifications
"""
serializer_class = CertificationsSerializer
queryset = Certifications.objects.all()
class EmployeesListCreateView(generics.ListCreateAPIView):
"""
List, Create all Employees
"""
serializer_class = EmployeesSerializer
queryset = Employees.objects.all()
class EmployeesRetrieveUpdateDeleteView(generics.RetrieveUpdateDestroyAPIView):
"""
Retrieve, Update or Delete an Employee
"""
serializer_class = EmployeesSerializer
def get_queryset(self):
return Employees.objects.all()
- Create another file in the app folder called serializers.py file.
from rest_framework import serializers
from .models import (
Certifications,
Employees
)
class CertificationsSerializer(serializers.ModelSerializer):
class Meta:
model = Certifications
fields = ['id', 'name', 'expiry_date']
class EmployeesSerializer(serializers.ModelSerializer):
class Meta:
model = Employees
fields = "__all__"
Step 3: Using Postman to CRUD data on the app
Postman is an excellent tool for testing API endpoints. With Postman, a user is able to provide data in JSON format that can be used to create, retrieve, update and delete data used on the app.
- To test our endpoint for retrieving employee objects, create an employee on the Django admin side that has a certification related to them.
Retrieving employee objects with their related certifications
- Send a GET request to the API endpoint for listing the employees in the database.
The certifications for the employees are not returned in the request response. This is because the certifications fields for the employee has not been properly serialized. Update the employee serializer class to include certifications and set read_only and many to True.
class EmployeesSerializer(serializers.ModelSerializer):
certifications = CertificationsSerializer(many=True, read_only=True)
class Meta:
model = Employees
fields = "__all__"
- Now sending a GET request to the API endpoint for listing the employees in the database will give the following response.
- Similarly, an API request to retrieve data for a specific employee will give the response below
Creating employee objects and their related certifications
- The image below shows how a POST request is sent to the URL endpoint to create an employee object that is visible on the Django admin site.
- Although the object for the employee is created, the related certification for the employee is not created by default. To create it, we have to override the create method in the employee serializer class.
class EmployeesSerializer(serializers.ModelSerializer):
certifications = CertificationsSerializer(many=True, read_only=True)
@transaction.atomic
def create(self, validated_data):
certifications_data = self.initial_data.get('certifications')
instance = super().create(validated_data)
if certifications_data:
Certifications.objects.bulk_create([
Certifications(
name=certification['name'],
expiry_date=certification['expiry_date'],
employees_id=instance.id
) for certification in certifications_data if certification.get('name')
])
# save the instance
instance.save()
return instance
class Meta:
model = Employees
fields = "__all__"
- Now a new employee and their related certifications may be created using a POST request to the API endpoint.
Updating employee objects and their related certifications in the nested fields
To make changes to the recently created employee, we send PATCH request to the http://127.0.0.1:8000/ API endpoint view. In this tutorial, we desire to perform the following changes;
- update the name of the existing certification for an employee
- create a new certification for the employee
- update the name of the employee
The certification data for the employees is not updated by default. To update the certification data, we need to override the update method on the employees serializer class.
@transaction.atomic
def update(self, instance, validated_data):
certifications_data = self.initial_data.get('certifications')
if certifications_data:
instance.certifications.clear()
for certification in certifications_data:
certification_id = certification.get("id")
if certification_id:
cert_obj = Certifications.objects.get(id=certification_id)
cert_obj.name = certification.get("name")
cert_obj.expiry_date = certification.get("expiry_date")
cert_obj.save()
instance.certifications.add(cert_obj)
else:
Certifications.objects.create(
name=certification.get("name"),
expiry_date=certification.get("expiry_date"),
employees_id=instance.id
)
instance = super().update(instance, validated_data)
instance.save()
return instance
Conclusion ๐ ๐
In this tutorial, we have highlighted the following;
- Creating Django database models with Foreign Key relations.
- Creating API POST, PATCH, GET, and DEL endpoints to create, update, retrieve and delete objects from the database. We also showcased how Postman may be used to make these requests.
- Overriding the create and update method of the serializer class to make changes to the nested fields within an object.
Next Steps
- View the full project code on GitHub.
- Test sending a DELETE request on Postman to delete one of the employee objects that have been created? What happens to the certifications that are related to this employee?
- Comments, questions or suggestions related to this demo? Please share them with me. I'll be delighted to hear from you.
Cheers! ๐ ๐ป ๐ ๐ป ๐ ๐ป