Using Django Rest Framework we can create rest endpoints, which can be used to create, read, update and delete (CRUD) objects. However, those endpoints are limited to modifying one object at a time. This becomes a huge problem when we want to create and update multiple objects at a time.
In this article, we are going to call endpoints only once instead of thousands of calls to endpoints for thousands of objects. By the end of this article, you should be able to perform bulk operations in the most efficient way.
Before following this blog please have a look at our previous blog where we implemented Bulk create using the Django rest framework.
So without further ado let’s get started!!
1. We will implement Update API using standard ListCreateView
. You can create a separate class for updating and inherit it with UpdateAPIView
.
2. We will write some utility functions such as validating IDs that the user wants to update.
3. We will use ListSerializer
class by customizing its behavior and bulk_update
methods to handle multiple data input objects.
We already created a project in our previous blog. We will reuse this code and add bulk update functionality in the same project.
Data Model: models.py
class Product(models.Model):
title = models.CharField(max_length=255)
description = models.TextField(max_length=500)
price = models.IntegerField()
type = models.CharField(max_length=255)
visible = models.BooleanField(default=False)
discount = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
The code inside models.py and urls.py remains the same, we don’t need to modify it.
views: views.py
1. Now let’s modify our ProductView inside of views.py.
def validate_ids(data, field="id", unique=True):
if isinstance(data, list):
id_list = [int(x[field]) for x in data]
if unique and len(id_list) != len(set(id_list)):
raise ValidationError("Multiple updates to a single {} found".format(field))
return id_list
return [data]
class ProductView(generics.ListCreateAPIView):
serializer_class = serializers.ProductSerializer
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get("data", {}), list):
kwargs["many"] = True
return super(ProductView, self).get_serializer(*args, **kwargs)
def get_queryset(self, ids=None):
if ids :
queryset = models.Product.objects.filter(id__in=ids)
else:
queryset = models.Product.objects.all()
return queryset
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
ids = validate_ids(request.data)
instances = self.get_queryset(ids=ids)
serializer = self.get_serializer(
instances, data=request.data, partial=False, many=True
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
2. ListSerializer
class provides the behavior for serializing and validating multiple objects at once. The list serializer will allow you to submit a request for multiple creates in a single call.
3. let’s override the get_serializer
method of the ListCreateAPIView
class to check whether the input data is list or not. If it is a list then set the property of kwargs["many"]=True
. By setting this property it will tell the serializer that it should use the list_serilizer_class
before calling individual updates for each object.
4. We also override the update method, which will perform validation on the ids
and return ids list which we will pass as input to the modified get_queryset
. The get_queryset
will return the instances that the user wants to update, which will further pass to the serializer to perform the bulk updates.
Serializer: serializers.py
1. By default, the ListSerializer
class does not support multiple updates. This is because the behavior that should be expected for insertions and deletions is ambiguous.
2. To support multiple updates you'll need to do so explicitly. When writing your multiple update code make sure to keep the following in mind:
3. How do you determine which instance should be updated for each item in the list of data?
4. How should insertions be handled? Are they invalid, or do they create new objects?
5. Now let’s modify our ProductBulkCreateUpdateSerializer inside of serializers.py.
6. We will use thebulk_update
, which allows you to perform bulk updates by passing a list of instances to update.
7. Now compute the instance_hash, so that we avoid needing to index into the instance.
class ProductBulkCreateUpdateSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
instance_hash = {index: i for index, i in enumerate(instance)}
result = [
self.child.update(instance_hash[index], attrs)
for index, attrs in enumerate(validated_data)
]
writable_fields = [
x
for x in self.child.Meta.fields
if x not in self.child.Meta.read_only_fields
]
try:
self.child.Meta.model.objects.bulk_update(result, writable_fields)
except IntegrityError as e:
raise ValidationError(e)
return result
8. That's it. We have implemented efficient bulk updates by customizing the ListSerializer
class behavior.
We have seen efficient bulk update by customizing the ListSerializer
class behavior and how it can be used to increase our app performance. A full working project can be found on GitHub in the Django bulk create and update repository.
Stay tuned to get the latest innovation from oxvsys and happy automation.