Greg C
Greg C

Reputation: 13

DRF-Filtering with multiple query params and foreign keys

I'm new to Django and I'm trying to create a route that can be called to retrieve an array of vehicles from my database, but I want the user to be able to provide multiple query params in the url (something like: http://127.0.0.1:8000/vehicles/?year=2020&make=Toyota). The problem that I have come across is that my vehicle model includes references to foreign keys for the make and the v_model (so named to avoid conflict with the Django "model"). I have a solution that doesn't seem very graceful. The fact that I have three nested conditional statements for each search field makes me suspicious. I tried using "filters.SearchFilter" but I was only able to provide a single value on which to base the search. So a request like this: http://127.0.0.1:8000/vehicles/?search=2020&search=Toyota would only search for vehicles with a make of "Toyota", ignoring the "year" parameter.

Is there some other way to do this that is cleaner or more "Django-approved"?

Here is my code:

models.py:

class Make(models.Model):
    name = models.CharField(max_length=200, unique=True)
    def __str__(self):
        return self.name


class VModel(models.Model):
    name = models.CharField(max_length=200, unique=True)
    make = models.ForeignKey(Make, on_delete=models.CASCADE)
    def __str__(self):
        return self.name


class Vehicle(models.Model):
    make = models.ForeignKey(Make, on_delete=models.CASCADE)
    v_model = models.ForeignKey(VModel, on_delete=models.CASCADE)
    year = models.CharField(max_length=5)
    def __str__(self):
        return self.year + " " + self.v_model.name

views.py:

Here is my attempt with filters.SearchFilter:

    queryset = Vehicle.objects.all()
    serializer_class = VehicleSerializer
    filter_backends = [filters.SearchFilter]
    search_fields = ['year', 'v_model__name','make__name']

And here is my "working" solution that seems hacky: (NOTE: I am using name__icontains so that if a user enters "Toyot" it will still get all cars with a make of Toyota).

class VehicleListViewSet(viewsets.ModelViewSet):
    serializer_class = VehicleSerializer
    queryset = Vehicle.objects.all()
    pagination_class = CustomPagination


    def get_queryset(self):
        qs = super().get_queryset()
        selected_make = self.request.query_params.get('make', None)
        if selected_make:
            try:
                found_make = Make.objects.get(name__icontains=selected_make)
            except:
                return []
            if found_make:
                if found_make.id:
                    qs = qs.filter(make=found_make.id)
        selected_v_model = self.request.query_params.get('v_model', None)
        if selected_v_model:
            try:
                found_v_model = VModel.objects.get(name__icontains=selected_v_model)
            except:
                return []
            if found_v_model:
                if found_v_model.id:
                    qs = qs.filter(v_model=found_v_model.id)
        selected_year = self.request.query_params.get('year', None)
        if selected_year:
            qs = qs.filter(year=selected_year)
        return qs

Upvotes: 1

Views: 3997

Answers (1)

Marco Lavagnino
Marco Lavagnino

Reputation: 1229

You shouldn't be using filters.SearchFilter. Instead, use a filterset_fields attribute in your ViewSet, like the example from this section of the documentation.

Your viewset would be like this:

class VehicleListViewSet(viewsets.ModelViewSet):
    serializer_class = VehicleSerializer
    queryset = Vehicle.objects.all()
    pagination_class = CustomPagination
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['year', 'v_model__name','make__name']

(Note there's no get_queryset override) You'll be able to query your API like this:

http://127.0.0.1:8000/vehicles/?year=2020&make__name=Toyota&v_model__name=Corolla

Upvotes: 5

Related Questions