简体   繁体   中英

Django REST API query on related field

I have 3 models, Run, RunParameter, RunValue:

class Run(models.Model):
    start_time = models.DateTimeField(db_index=True)
    end_time = models.DateTimeField()

class RunParameter(models.Model):
    parameter = models.ForeignKey(Parameter, on_delete=models.CASCADE)

class RunValue(models.Model):
    run = models.ForeignKey(Run, on_delete=models.CASCADE)
    run_parameter = models.ForeignKey(RunParameter, on_delete=models.CASCADE)
    value = models.FloatField(default=0)

    class Meta:
            unique_together=(('run','run_parameter'),)

A Run can have a RunValue , which is a float value with the value's name coming from RunParameter (which is basically a table containing names), for example:

A RunValue could be AverageTime , or MaximumTemperature

A Run could then have RunValue = RunParameter:AverageTime with value X.

Another Run instance could have RunValue = RunParameter:MaximumTemperature with value Y, etc.

I created an endpoint to query my API, but I only have the RunParameter ID (because of the way you can select which parameter you want to graph), not the RunValue ID directly. I basically show a list of all RunParameter and a list of all Run instances, because if I showed all instances of RunValue the list would be too long and confusing, as instead of seeing "Maximum Temperature" you would see:

  • "Maximum Temperature for Run X"

  • "Maximum Temperature for Run Y"

  • "Maximum Temperature for Run Z", etc. (repeat 50+ times).

My API view looks like this:

class RunValuesDetailAPIView(RetrieveAPIView):
    queryset = RunValue.objects.all()
    serializer_class = RunValuesDetailSerializer
    permission_classes = [IsOwnerOrReadOnly]]

And the serializer for that looks like this:

class RunValuesDetailSerializer(ModelSerializer):
    run = SerializerMethodField()
    class Meta:
        model = RunValue
        fields = [
            'id',
            'run',
            'run_parameter',
            'value'
        ]
    def get_run(self, obj):
        return str(obj.run)

And the URL just in case it's relevant:

url(r'^run-values/(?P<pk>\d+)/$', RunValuesDetailAPIView.as_view(), name='values_list_detail'),

Since I'm new to REST API, so far I've only dealt with having the ID of the model API view I am querying directly, but never an ID of a related field. I'm not sure where to modify my queryset to pass it an ID to get the appropriate model instance from a related field.

At the point I make the API query, I have the Run instance ID and the RunParameter ID. I would need the queryset to be:

run_value = RunValue.objects.get(run=run_id, run_parameter_id=param_id)

While so far I've only ever had to do something like:

run_value = RunValue.objects.get(id=value_id) # I don't have this ID

If I understand correctly, you're trying to get an instance of RunValue with only the Run id and the RunParameter id, ie query based on related fields.

The queryset can be achieved with the following:

run_value = RunValue.objects.get(
                          run__id=run_id,
                          run_parameter__id=run_parameter_id
            )

Providing that a RunValue instance only ever has 1 related Run and RunParameter , this will return the instance of RunValue you're after.

Let me know if that's not what you mean.

The double underscore allows you to access those related instance fields in your query.

Well its pretty simple, all you have to do is override the get_object method, for example(copy pasted from documentation ):

# view
from django.shortcuts import get_object_or_404

class RunValuesDetailAPIView(RetrieveAPIView):
    queryset = RunValue.objects.all()
    serializer_class = RunValuesDetailSerializer
    permission_classes = [IsOwnerOrReadOnly]]
    lookup_fields = ["run_id", "run_parameter_id"]

    def get_object(self):
        queryset = self.get_queryset()             # Get the base queryset
        queryset = self.filter_queryset(queryset)  # Apply any filter backends
        filter = {}
        for field in self.lookup_fields:
            if self.kwargs[field]: # Ignore empty fields.
                filter[field] = self.kwargs[field]
        obj = get_object_or_404(queryset, **filter)  # Lookup the object
        self.check_object_permissions(self.request, obj)
        return obj

# url
url(r'^run-values/(?P<run_id>\d+)/(?P<run_parameter_id>\d+)/$', RunValuesDetailAPIView.as_view(), name='values_list_detail'),

But one big thing you need to be careful, is not to have duplicate entries with same run_id and run_parameter_id , then it will throw errors. To avoid it, either use unique_together=['run', 'run_parameter'] or you can use queryset.filter(**filter).first() instead of get_object_or_404 in the view. But second option will produce wrong results when duplicate entries are created.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM