Search code examples
djangopython-3.xparallel-processingdjango-rest-frameworkapi-design

How to handle parallel PUT/PATCH requests in Django Rest Framework? (Overwrite issue)


I'm developing a Web App for a uni project and it's about airports and carriers...

Problem

The problem is when I try to feed to the database I'm doing in a parallel way because in the json file I'm using as the reference there is a lot of entries about carriers and airports.

When you update(PUT and PATCH) one by one in a sequential way it is working as it should but when I try PUT/PATCH parallel requests the instance in the DB is being overwritten every time.

Each airport has a list of carriers and when I tried to add more carriers to that list via PUT or PATCH in a parallel way it overwrites the instance previous carriers list

My models are:

class Carrier(models.Model):
    code = models.CharField(max_length=10, primary_key=True)
    name = models.TextField()

    def __str__(self):
        return self.name

class Airport(models.Model):
    code = models.CharField(max_length=10, primary_key=True)
    name = models.TextField()
    carriers = models.ManyToManyField(Carrier, related_name='airports')

    def __str__(self):
        return self.name

Serializers:

class AirportListSerializer(serializers.ModelSerializer):
    url = serializers.HyperlinkedIdentityField(view_name='airport-detail')

    class Meta:
        model = models.Airport
        fields = ('code', 'name', 'url')

class AirportDetailSerializer(serializers.ModelSerializer):
    url = serializers.HyperlinkedIdentityField(view_name='airport-detail')

    class Meta:
        model = models.Airport
        fields = ('code', 'name', 'url', 'carriers')

And I'm having problems with my update method I had to override it because for appending new data to the carriers array of an instance of an airport:

def update(self, request, *args, **kwargs):
        instance = self.get_object()

        serializer = serializers.AirportDetailSerializer(
            instance=instance,
            data=request.data,
            context={'request': request}
        )

        if serializer.is_valid(raise_exception=True):
            # Getting the user inputed carriers after it was validated by the serializer
            carriers = set(dict(request.data)['carriers'])    
            # Adding new carriers to the current airport list of carriers without deleting the old ones
            for carrier in serializer.validated_data['carriers']:
                print(carrier)
                carriers.add(carrier)
            print('Carriers %s' % carriers)
            # Saving alterations to the db
            serializer.save(carriers=carriers)

        # Overriding the original data for more features
        data = serializer.data
        # Creating the carrier links 
        data['carriers'] = ['http://%s/api/carriers/%s/' % (request.get_host(), carrier) for carrier in data['carriers']]

        return Response(data)

Example of entry in DRF Browsable API: enter image description here


Solution

  • Every time you are calling serializer.save(carriers=carriers), you are saving the Airport instance with only the carriers from your particular PUT/PATCH request...rather than adding carriers to your Airport instance.

    You should look at the docs for writable nested serializers and use a separate CarrierSerializer.

    On a related note, your update logic is better suited as part of the AirportSerializer, not your view. This makes it more reusable. That section of the DRF docs is the perfect example for you.

    Something like this pseudo-code adapted from the Albums/Tracks example:

    class AirportSerializer(serializers.ModelSerializer):
        carriers = CarrierSerializer(many=True)
    
        class Meta:
            model = Airport
            fields = ......
    
        def update(self, instance, validated_data):
            carriers_data = validated_data.pop('carriers')
            for carrier in carriers_data:
                Carrier.objects.update_or_create(airport=instance, defaults=carrier_data)
            return instance