DRF - Creating data in multiple models with one request


In my application, there are two tables; ‘Calibration’, and ‘SetPointResult’.

A Calibration can have one to many SetPointResult’s, and currently I implement this via a foreign key in the ‘SetPointResult’ table that references which ‘Calibration’ it belongs to. However, I want to POST data from my front-end containing both the data to create the entry in ‘Calibration’ and the multiple entries in ‘SetPointResult’.

I currently do it in 2 API calls, but if an error occurs when inserting the SetPointResults, then there is an empty Calibration entry that is now invalid.

I am tempted to for simplicities sake define a set number of results and have nullable foreign keys on the Calibration table but this just feels like bad design.

Any help would be awesome, thanks!

Use a custom DRF serializer to take data in (from serializer.validated_data) and do a custom function/method in a service/handler that will save it however you like.

I can’t do this because the SetPointResult has a foreign key to the Calibration entry, so it won’t pass validation as the Calibration entry must be made first to get the ID.

For the moment I’m using the create method on my ModelViewSet, creating the Calibration, getting its id and inserting it into the data for the SetPointResult’s and then creating them. I’ve decorated the create function as atomic as well so if something goes wrong with the creation of the SetPointResult’s after the Calibration, none of it will be committed.

    def create(self, request, *args, **kwargs):      
        set_point_results = request.data.pop("set_point_results")
        calibration_serializer = self.get_serializer(data=request.data)
        for set_point_result in set_point_results:
            set_point_result['calibration'] = calibration_serializer.data['id']

        results_serializer = SetPointResultSerializer(many=True, data=set_point_results)

        response_data = calibration_serializer.data
        response_data['set_point_results'] = results_serializer.data

        return Response(response_data, status=status.HTTP_201_CREATED)

This seems to work but I feel like I’m still not doing things the best way