Django Rest Framework unique field constraint on array

So, I'm trying to make an endpoint where I insert a list of objects.
My issue is the behavior and response when inserting duplicates.

What I want to accomplish is to:

  1. Send the duplicate lead external_id(s) in the error response
  2. Insert any other non duplicated object

I'm pretty sure this logic (for the response and behavior) could be accomplished in the modifying the serializer.is_valid() method... but before doing that, I wanted to know if anyone had experience with this kind of request.. Maybe there is a "clean" way to do this while keeping the unique validation in the model.


Data on OK response:

[
  {
    "external_id": "1", 
    "phone_number": "1234567890"
  }
]

Data for a FAIL request (1 is dup, but 2 should be inserted. Expecting a response like "external_id" : "id 1 is duplicated"):

[
  {
    "external_id": "1", 
    "phone_number": "1234567890"
  },
  {
    "external_id": "2", 
    "phone_number": "2234567890"
  }
]

models.py

class Lead(models.Model):
    external_id = models.CharField(max_length=20, unique=True)
    phone_number = models.CharField(max_length=50)

serializers.py

class LeadSerializer(serializers.ModelSerializer):
    class Meta:
        model  = Lead
        fields = '__all__'

    def create(self, validated_data):
        lead = Lead.objects.create(**validated_data)
        log.info(f"Lead created: {lead.import_queue_id}")
        return lead

views.py

class LeadView(APIView):
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    @extend_schema(description="Insert campaign data", request=LeadSerializer(many=True), responses=None, tags=["Leads"])
    def post(self, request):
        serializer = LeadSerializer(data=request.data, many=True)

        valid = serializer.is_valid()
        if serializer.is_valid():
            serializer.save()
            return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
        else:
            return Response({"status": "error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

You can customize the behaviour of list of object through list_serializer_class option in meta class. Like this:

class LeadListSerializer(serializers.ListSerializer):
    def validate(self, data):
       items = list(map(lambda x: x['phone_number'], data))
       if len(set(items)) == len(items)):
           return super().validate(data)
       raise ValidationError()
       
class LeadSerializer(serializers.ModelSerializer):
    class Meta:
        model  = Lead
        fields = '__all__'
        list_serializer_class = LeadListSerializer
Back to Top