Introduction
Django REST Framework provides a way to bulk create and update objects with a single request. This is useful when you want to create or update a large number of objects. In this post, we’ll see how to bulk create and update objects with Django REST Framework.
Before we start, let’s take a look at some code snippets from a simple Django REST Framework project.
models.py
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=100)
serializers.py
from rest_framework import serializers
from .models import Person
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = ('id', 'name')
views.py
from rest_framework import viewsets
from .models import Person
from .serializers import PersonSerializer
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
urls.py
from django.conf.urls import url, include
from rest_framework import routers
from .views import PersonViewSet
router = routers.DefaultRouter()
router.register(r'people', PersonViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
tests.py
from django.test import TestCase
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from .models import Person
class PersonTests(APITestCase):
def test_create_person(self):
"""
Ensure we can create a new person object.
"""
url = reverse('person-list')
data = {'name': 'John'}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Person.objects.count(), 1)
self.assertEqual(Person.objects.get().name, 'John')
def test_update_person(self):
"""
Ensure we can update an existing person object.
"""
person = Person.objects.create(name='John')
url = reverse('person-detail', kwargs={'pk': person.pk})
data = {'name': 'Jane'}
response = self.client.put(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(Person.objects.count(), 1)
self.assertEqual(Person.objects.get().name, 'Jane')
This code allows us to create and update a single Person
object with a single request. Let’s see how we can bulk create and update objects with Django REST Framework.
Bulk create and update with Django REST Framework
Django REST Framework provides a way to bulk create and update objects with a single request. This is useful when you want to create or update a large number of objects. The trick is to provide a list_serializer_class
attribute in the Meta
class of the serializer. The list_serializer_class
attribute should be set to a class that inherits from the ListSerializer
class. In the ListSerializer
class, we can override the create
and update
methods to bulk create and update objects.
Before we implement the serializer, let’s update our tests to check if we can bulk create and update objects.
class PersonTests(APITestCase):
def test_create_person(self):
"""
Ensure we can create a new person object.
"""
url = reverse('person-list')
data = {'name': 'John'}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Person.objects.count(), 1)
self.assertEqual(Person.objects.get().name, 'John')
def test_create_people(self):
"""
Ensure we can bulk create new person objects.
"""
url = reverse('person-list')
data = [
{'id': 1, 'name': 'John'},
{'id': 2, 'name': 'Jane'},
{'id': 3, 'name': 'Jack'},
]
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Person.objects.count(), 2)\
# check that Person object with name 'John' exists
self.assertTrue(Person.objects.filter(name='John').exists())
# check that Person object with name 'Jane' exists
self.assertTrue(Person.objects.filter(name='Jane').exists())
# check that Person object with name 'Jack' does not exist
self.assertFalse(Person.objects.filter(name='Jack').exists())
def test_update_person(self):
"""
Ensure we can update an existing person object.
"""
person = Person.objects.create(name='John')
url = reverse('person-detail', kwargs={'pk': person.pk})
data = {'name': 'Jane'}
response = self.client.put(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(Person.objects.count(), 1)
self.assertEqual(Person.objects.get().name, 'Jane')
def test_update_people(self):
"""
Ensure we can bulk update existing person objects.
"""
john = Person.objects.create(name='John')
jane = Person.objects.create(name='Jane')
url = reverse('person-list')
data = [{'id': 1, 'name': 'John Doe'}, {'id': 2, 'name': 'Jane Doe'}]
response = self.client.put(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(Person.objects.count(), 2)
self.assertEqual(Person.objects.get(pk=john.pk).name, 'John Doe')
self.assertEqual(Person.objects.get(pk=jane.pk).name, 'Jane Doe')
This should fail because we haven’t implemented the ListSerializer class yet. Let’s implement the ListSerializer class.
Serializers
Let’s edit the serializers.py
file to implement the ListSerializer class, PersonListSerializer
and pass it to the list_serializer_class
attribute in the Meta
class of the PersonSerializer
class.
serializers.py
from rest_framework import serializers
from .models import Person
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = ('id', 'name')
list_serializer_class = PersonListSerializer
class PersonListSerializer(serializers.ListSerializer):
def create(self, validated_data):
people = [Person(**item) for item in validated_data]
return Person.objects.bulk_create(people)
def update(self, instance, validated_data):
mapping = {person.id: person for person in instance}
data_mapping = {item['id']: item for item in validated_data}
people = []
for person_id, data in data_mapping.items():
person = mapping.get(person_id, None)
if person:
people.append(self.child.update(person, data))
return people
In the create method, we pass the validated data to the Person
model and create a list of Person
objects. Then, we use the bulk_create
method to create the objects in the database. In the update method, we create a mapping of the Person
objects and the validated data. Then, we loop through the validated data and update the Person
objects.
No changes are required in the views and urls. The views and urls remain the same as before.
views.py
from rest_framework import viewsets
from .models import Person
from .serializers import PersonSerializer
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
urls.py
from django.urls import path, include
from .views import PersonViewSet
router = DefaultRouter()
router.register(r'people', PersonViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Conclusion
In this post, we saw how we could bulk create and update objects with Django REST Framework. By using the list_serializer_class
attribute, we can override the create
and update
methods to bulk create and update objects. I hope you found this post useful. Thanks for reading!