Bulk Update with Django Rest Framework

  • Akshay Pawar
  • Mar 22, 2022
django rest framework , django

Introduction

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!!


Objectives

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.


Project Setup

We already created a project in our previous blog. We will reuse this code and add bulk update functionality in the same project.


Implementation


Data Modelmodels.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.


viewsviews.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.


Serializerserializers.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.


Summary

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.

Copyright © 2022 Oxvsys Automation Technologies Pvt. Ltd.