'DRF - Merge multiple querysets in one ModelViewSet with different models order by time

I am trying to combine three queryset in one which are from different model classes and trying to get json result ordered by time. Each models have time field. I want to merge all of the fields together and want to make a single queryset that can be send to api end point.

models.py

class DoseRate(models.Model):
    device = models.ForeignKey(Device, on_delete=models.CASCADE ,related_name='dose_rates')
    time = models.DateTimeField(_("Time"), default=timezone.now)
    cpm = models.IntegerField(_("Counts per minute"), default=0)
    uSv = models.FloatField(_("Dose rate in uSv"), default=0)
    time_diff = models.IntegerField(_("Time difference"), default=0)
    total_counts = models.IntegerField(_("Total counts"), default=0)
    total_time = models.IntegerField(_("Total time"), default=0)
    avg_count = models.FloatField(_("Average count"), default=0)
    std_dev = models.FloatField(_("Standard deviation"), default=0)
    
class GPS(models.Model):
    device = models.ForeignKey(Device, on_delete=models.CASCADE ,related_name='gps')
    uSv = models.FloatField(_("Dose rate in uSv"), default=0)
    time = models.DateTimeField(_("Time"), default=timezone.now)
    latitude = models.FloatField(_("Latitude"), default=0)
    longitude = models.FloatField(_("Longitude"), default=0)
    address = models.CharField(_("Address"), max_length=200, blank=True, null=True, default=None)
    
class DeviceStatus(models.Model):
    device = models.ForeignKey(Device, on_delete=models.CASCADE, related_name='device_status')
    time = models.DateTimeField(_("Time"), default=timezone.now)
    temprature = models.FloatField(_("Temprature"), default=0)
    frequency = models.IntegerField(_("Frequency"), default=0)
    voltage = models.FloatField(_("Voltage"), default=0)
    battery_level = models.IntegerField(_("Battery level"), default=0)

serializers.py

class AJAXSerializer(serializers.ModelSerializer): 
    class Meta:
        model = DoseRate # This only provide the data for the avilaible fields
        fields = ( 'cpm', 'uSv', 'time_diff', 'total_counts', 'total_time', 'avg_count', 'std_dev',)
    
    def __init__(self, *args, **kwargs):
        
        super(AJAXSerializer, self).__init__(*args, **kwargs)
        self.request = self.context['request']
        self.device = Device.objects.filter(user=self.request.user)
        self.gps = GPS.objects.filter(device__in=self.device)
        self.dose_rate = DoseRate.objects.filter(device__in=self.device)
        self.device_status = DeviceStatus.objects.filter(device__in=self.device)


class AJAXSerializer(serializers.Serializer): # Fields that are not avilable in the model gives an error
    device = serializers.CharField(max_length=100)
    cpm = serializers.IntegerField()
    uSv = serializers.FloatField()
    time_diff = serializers.FloatField()
    total_counts = serializers.IntegerField()
    total_time = serializers.FloatField()
    avg_count = serializers.FloatField()
    std_dev = serializers.FloatField()
    latitude = serializers.FloatField()
    longitude = serializers.FloatField()
    temprature = serializers.FloatField()
    frequency = serializers.IntegerField()
    voltage = serializers.FloatField()
    battery_level = serializers.FloatField()
    time = serializers.DateTimeField(read_only=True)
    
    def __init__(self, *args, **kwargs):
        super(AJAXSerializer, self).__init__(*args, **kwargs)
        self.request = self.context['request']
        self.device = Device.objects.filter(user = self.request.user)
    
    def create(self, validated_data):
        return AJAXSerializer(**validated_data)

views.py

class AJAXViewSet(viewsets.ModelViewSet):
    dose_rate = DoseRate.objects.filter()
    gps = GPS.objects.filter()
    device_status = DeviceStatus.objects.filter()
    result_list = sorted(chain(dose_rate, gps, device_status), key= attrgetter('time'))
    queryset =  result_list
    serializer_class = AJAXSerializer
    allowed_methods = ('GET',)
    authentication_classes = (SessionAuthentication,)
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        return self.queryset

I am looking for this output where if any queryset hasn't data at the time when other queryset has it will take the last data.

[
    {
        "time": "2019-01-01T00:00:00Z",
        "cpm": 100,
        "uSv": 0.57,
        "time_diff": 1200,
        "total_counts": 100,
        "total_time": 1200,
        "avg_count": 100,
        "std_dev": 0.57,
        "latitude": 0,
        "longitude": 0,
        "temperature": 0,
        "frequency": 0,
        "battery_level": 0,
        "device_id": "1",
    },
    {
        ...
    }
]

But I am getting this output.

[
    {
        "cpm": 40,
        "uSv": 0.228,
        "time_diff": 2200,
        "total_counts": 300,
        "total_time": 0,
        "avg_count": 0.0,
        "std_dev": 0.0
    },
    {
        "uSv": 0.228
    },
    {
        "cpm": 35,
        "uSv": 0.2,
        "time_diff": 1250,
        "total_counts": 600,
        "total_time": 0,
        "avg_count": 0.0,
        "std_dev": 0.0
    },
    {
        "cpm": 32,
        "uSv": 0.182,
        "time_diff": 1200,
        "total_counts": 500,
        "total_time": 0,
        "avg_count": 0.0,
        "std_dev": 0.0
    },
]


Solution 1:[1]

Finaly I have solve this problem by adding SerializerMethodField in ModelSerializer.

serializers.py

class AJAXSerializer(serializers.ModelSerializer): 

    def __init__(self, *args, **kwargs):
        super(AJAXSerializer, self).__init__(*args, **kwargs)
        self.request = self.context['request']
        self.device = Device.objects.filter(user=self.request.user)
        self.gps = GPS.objects.filter(device__in=self.device)
        self.dose_rate = DoseRate.objects.filter(device__in=self.device)
        self.device_status = DeviceStatus.objects.filter(device__in=self.device)

    class Meta:
        model = DoseRate # This only provide the data for the avilaible fields
        fields = "__all__"
    
    latitude = serializers.SerializerMethodField()
    longitude = serializers.SerializerMethodField()
    temprature = serializers.SerializerMethodField()
    frequency = serializers.SerializerMethodField()
    voltage = serializers.SerializerMethodField()
    battery_level = serializers.SerializerMethodField()
    
    def get_latitude(self, obj):
        return self.gps.filter(device=obj.device,time__lte = obj.time).order_by('-time').values_list('latitude', flat=True).first()
    def get_longitude(self, obj):
        return self.gps.filter(device=obj.device,time__lte = obj.time).order_by('-time').values_list('longitude', flat=True).first()
    def get_temprature(self, obj):
        return self.device_status.filter(device=obj.device,time__lte = obj.time).order_by('-time').values_list('temprature', flat=True).first()
    def get_frequency(self, obj):
        return self.device_status.filter(device=obj.device,time__lte = obj.time).order_by('-time').values_list('frequency', flat=True).first()
    def get_voltage(self, obj):
        return self.device_status.filter(device=obj.device,time__lte = obj.time).order_by('-time').values_list('voltage', flat=True).first()
    def get_battery_level(self, obj):
        return self.device_status.filter(device=obj.device,time__lte = obj.time).order_by('-time').values_list('battery_level', flat=True).first()

However I am only taking the DoseRate model in my ModelViewSet to increase performance by only taking one queryset time.

views.py

class AJAXViewSet(viewsets.ModelViewSet):
    queryset = DoseRate.objects.filter()
    serializer_class = AJAXSerializer
    allowed_methods = ('GET',)
    authentication_classes = (SessionAuthentication,)
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        return self.queryset

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ahnaf Tahmid Chowdhury