In Django REST Framework, how do filter_backends let clients filter, search and order a list endpoint such as GET /jobs/?
answer
- a list of classes on the view
- each narrows the previous queryset
- django-filter supplies the field filters
- ?search= and ?ordering= parameters
- empty default, view list replaces it
basics
~10 sA DRF generic view passes its queryset through each class in filter_backends: DjangoFilterBackend (django-filter) handles field filters like ?remote=true, SearchFilter handles ?search= over search_fields, and OrderingFilter handles ?ordering= limited to ordering_fields.
solid answer
~40 sDRF generic views pass their queryset through every class listed in `filter_backends`, in order, and each backend returns a narrower or reordered `QuerySet`. `DjangoFilterBackend` comes from the separate django-filter package and turns `filterset_fields` (or a `filterset_class`) into per-field parameters such as `?remote=true`. `SearchFilter` reads `?search=` and matches the terms against `search_fields`; `OrderingFilter` reads `?ordering=` and applies `order_by()` limited to `ordering_fields`, falling back to the view's `ordering`. `DEFAULT_FILTER_BACKENDS` defaults to an empty list, and a view-level `filter_backends` replaces that default rather than adding to it. The backends run in `list()` and also inside `get_object()`, so a filter parameter can turn a detail request into a 404.
code
python · 17 linesfrom django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters, generics
from jobs.models import Job
from jobs.serializers import JobSerializer
class JobListView(generics.ListAPIView):
queryset = Job.objects.select_related("company")
serializer_class = JobSerializer
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ["remote", "seniority", "company"]
search_fields = ["title", "description", "company__name"]
ordering_fields = ["posted_at", "salary_max"]
ordering = ["-posted_at"]
# GET /jobs/?remote=true&search=python&ordering=-salary_maxgo deeper
Name the three backends, the query parameter each reads and the view attribute each needs. Know that django-filter is a separate install that goes into INSTALLED_APPS.
Explain that filter_queryset chains backends in list order, that the global default is empty, and that a view's filter_backends replaces the default instead of extending it.
Point out that backends also run inside get_object, so filter parameters can 404 detail and write requests, and decide which rules belong in get_queryset versus a reusable backend.
Frame filter backends as the API's public query surface: which parameters are documented and supported long term, and how a shared backend keeps dozens of list endpoints consistent.
## What a filter backend is In Django REST Framework (DRF), a **filter backend** is a small class that takes the view's queryset and returns a narrower or reordered one. Every backend subclasses `BaseFilterBackend` and implements one method, `filter_queryset(self, request, queryset, view)`. The backend reads the request (usually `request.query_params`) and attributes declared on the view, and returns a new `QuerySet`. Because Django QuerySets are lazy, each backend only adds clauses to the SQL that will eventually run; nothing reaches the database until the view paginates or serializes the result. The backends a view uses are listed in its `filter_backends` attribute. `GenericAPIView.filter_queryset()` loops over that list **in order** and feeds each backend the output of the previous one, so the backends compose like a pipeline. ## The three backends you will meet | Backend | Comes from | Query parameter | View attribute it reads | What it adds | |---|---|---|---|---| | `DjangoFilterBackend` | the `django-filter` package (`django_filters.rest_framework`) | one per field, e.g. `?remote=true` | `filterset_fields` or `filterset_class` | field-by-field filters | | `SearchFilter` | `rest_framework.filters` | `?search=` (setting `SEARCH_PARAM`) | `search_fields` | a free-text match across several fields | | `OrderingFilter` | `rest_framework.filters` | `?ordering=` (setting `ORDERING_PARAM`) | `ordering_fields`, `ordering` | an `order_by()` chosen by the client | For a job-listings API, `GET /jobs/?remote=true&search=python&ordering=-salary_max` keeps remote jobs, then keeps those whose title, description or company name contains 'python', then sorts by maximum salary, highest first. - `filterset_fields` gives simple **equality** filters: django-filter builds a `FilterSet` for the listed fields automatically. For ranges, choice lists or custom lookups you write a `FilterSet` and point `filterset_class` at it. - `SearchFilter` does nothing unless the view sets `search_fields` **and** the request carries a non-empty search term. - `OrderingFilter` acts even without `ordering_fields` (it then allows the serializer's readable fields) and falls back to the view's `ordering` attribute when the client sends no valid ordering. ## Configuring backends: global default vs per view The project-wide default lives in the `REST_FRAMEWORK` settings dict under `DEFAULT_FILTER_BACKENDS`. Its default value is an **empty list**, so a fresh DRF project filters nothing: parameters such as `?remote=true` are simply ignored until a backend is configured. To use django-filter you: 1. install the `django-filter` package; 2. add `'django_filters'` to `INSTALLED_APPS`; 3. list `django_filters.rest_framework.DjangoFilterBackend` in `DEFAULT_FILTER_BACKENDS` or in a view's `filter_backends`. The per-view `filter_backends` is a plain class attribute whose default **is** the setting. Assigning it on a view therefore **replaces** the global list. A project that sets `DjangoFilterBackend` globally and then writes `filter_backends = [filters.SearchFilter]` on one view has silently switched off field filtering on that view; repeat every backend you still want. ## Where the backends run `filter_queryset()` is called from two places in DRF's generic machinery: - `ListModelMixin.list()` runs `self.filter_queryset(self.get_queryset())` before pagination and serialization; - `GenericAPIView.get_object()` runs the same expression before looking the object up by `lookup_field`. The second call surprises people: filters also apply to retrieve, update, partial update and destroy. `GET /jobs/42/?remote=false` returns 404 for a remote job, because the filtered queryset no longer contains it. `create()` never touches the queryset, so filter parameters have no effect on `POST /jobs/`. ## Common mistakes - Expecting filtering with no configuration, when `DEFAULT_FILTER_BACKENDS` is empty. - Importing `DjangoFilterBackend` from `rest_framework.filters`: DRF removed its own copy in 3.7 and the class lives in django-filter. - Forgetting that a view-level `filter_backends` drops the global backends. - Treating a client-controlled filter parameter as a security boundary; what a client may omit restricts nothing. ## Writing your own backend When the same narrowing is needed on many views, subclass `BaseFilterBackend` and override `filter_queryset()`. Implement `get_schema_operation_parameters()` if the parameter should appear in the generated OpenAPI schema, and `to_html()` if it should render a control in the browsable API. The method must return a `QuerySet` so the next backend and the paginator can keep chaining.
- How would you add a filter that every job view needs, such as hiding draft listings, without repeating get_queryset() everywhere?Subclass `BaseFilterBackend`, override `filter_queryset(self, request, queryset, view)` to return `queryset.filter(status="published")`, and add the class to `DEFAULT_FILTER_BACKENDS` or to each view's `filter_backends`. It runs in `list()` and `get_object()` like any backend. Remember that a view which sets its own `filter_backends` must include it again, or the rule disappears there.
- Do filter backends affect POST /jobs/ on a ModelViewSet?No. `CreateModelMixin.create()` validates the body and saves through the serializer without calling `get_queryset()` or `filter_queryset()`. Backends run only where the queryset is read: `list()`, and every action that goes through `get_object()`, namely retrieve, update, partial update and destroy.
Filter backends are stations on a sorting line: each station takes the tray the previous one passed on and sifts it further. Installing your own line on one view replaces the whole factory line there, it does not bolt stations onto it.
saying these in an interview costs you the question
- DRF filters list endpoints out of the box with no configuration
- Setting filter_backends on a view adds to DEFAULT_FILTER_BACKENDS
- DjangoFilterBackend is imported from rest_framework.filters
- Filter backends only run on list requests, never on detail lookups
- SearchFilter searches every model field when search_fields is omitted