Welcome back! In our last lesson, you made your API more scalable and user-friendly by implementing pagination. Now, instead of sending a massive, unwieldy list of tasks, your API serves them in neat, manageable pages. This is a huge step towards building a production-ready system.
Today, we'll give your API another layer of sophistication: filtering, searching, and ordering. Pagination controls how much data is returned, but filtering controls which data is returned. This empowers clients to request exactly what they need—for example, only "in-progress" tasks, tasks containing the word "report," or tasks sorted by priority.
By the end of this lesson, you will be able to:
- Implement exact-match filtering for specific model fields.
- Add a text-based search capability to your API endpoint.
- Allow clients to order the result set based on different attributes.
- Create a custom
FilterSetfor advanced filtering logic, such as date ranges or numerical comparisons.
This is a fundamental skill in API design and directly supports the Infilect job description's requirement to "design and develop REST APIs for frontend and backend." A well-designed API doesn't just expose data; it provides powerful tools for clients to interact with that data efficiently.
1. The Three Pillars of Query Customization
Django REST Framework (DRF) provides a powerful and declarative way to add these features through a system called filter backends. We will focus on three key backends:
DjangoFilterBackend: For granular, field-level filtering based on exact matches (e.g.,?completed=true).SearchFilter: For text-based, partial-match searching across multiple fields (e.g.,?search=meeting).OrderingFilter: For allowing the client to specify the sort order of the results (e.g.,?ordering=-priority).
Let's integrate these into our TaskMaster project one by one.
2. Basic Filtering with DjangoFilterBackend
The most common requirement is to filter a list by the value of a specific field. To do this, we'll use the popular django-filter library, which integrates seamlessly with DRF.
The video "Filtering, Searching, and Ordering" by Cryce Truly provides a clear, step-by-step walkthrough of this setup.
Filtering, Searching, and Ordering. Django Rest Framework complete tutorial.#12
This video covers the initial setup for filtering, from installation to implementation in a view.
First, watch the section on installing the library. Then, follow along with the section on configuring the view to see how the filter_backends and filterset_fields attributes are used.
Your Task: Implement Basic Filtering
-
Install the library. Stop your development server and run:
pip install django-filter -
Add it to
INSTALLED_APPS. In yourmyproject/settings.pyfile, add'django_filters'to theINSTALLED_APPSlist.# myproject/settings.py INSTALLED_APPS = [ # ... other apps 'rest_framework', 'rest_framework_simplejwt', 'tasks', 'django_filters', # Add this line ] -
Configure your
TaskViewSet. Opentasks/views.pyand modify it to use theDjangoFilterBackend. We'll allow filtering on thecompletedstatus andpriority.# tasks/views.py from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated from django_filters.rest_framework import DjangoFilterBackend # Import this from .models import Task from .serializers import TaskSerializer from .pagination import CustomTaskPagination class TaskViewSet(viewsets.ModelViewSet): queryset = Task.objects.all() serializer_class = TaskSerializer permission_classes = [IsAuthenticated] pagination_class = CustomTaskPagination # Add these lines for filtering filter_backends = [DjangoFilterBackend] filterset_fields = ['completed', 'priority']Note: The video and some older tutorials use
filter_fields. The modern and correct attribute name used bydjango-filterisfilterset_fields.
Now, restart your server. You can make authenticated requests to your API with new query parameters:
http://127.0.0.1:8000/api/tasks/?completed=truewill show only completed tasks.http://127.0.0.1:8000/api/tasks/?priority=Hwill show only high-priority tasks.
3. Adding Text-Based Search
Filtering by exact matches is great, but what if a user wants to find a task by searching for a word in its title or description? That's where SearchFilter comes in.
Filtering, Searching, and Ordering. Django Rest Framework complete tutorial.#12
The same video continues to demonstrate how to add the SearchFilter.
Watch the segment from . It shows how to add SearchFilter to the filter_backends list and define which fields the search should apply to using search_fields.
Your Task: Implement Search
Let's update tasks/views.py to enable searching on the title and description fields.
# tasks/views.py
from rest_framework import viewsets, filters # Import filters
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
# ... (other imports)
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = [IsAuthenticated]
pagination_class = CustomTaskPagination
# Update filter_backends and add search_fields
filter_backends = [
DjangoFilterBackend,
filters.SearchFilter, # Add SearchFilter
]
filterset_fields = ['completed', 'priority']
search_fields = ['title', 'description'] # Define searchable fields
Note: Make sure to import filters from rest_framework.
With this change, you can now make requests like:http://127.0.0.1:8000/api/tasks/?search=monthly%20report
DRF will automatically generate a database query that looks for "monthly" or "report" in the title and description fields of your tasks.
4. Allowing Client-Side Ordering
Finally, let's give the client the ability to sort the results. This is handled by the OrderingFilter.
Filtering, Searching, and Ordering. Django Rest Framework complete tutorial.#12
The video concludes by explaining how to add the OrderingFilter.
Watch the final segment from . It follows the same pattern: add OrderingFilter to the backends and define the fields the client can order by.
Your Task: Implement Ordering
Let's allow clients to order tasks by their creation date (created_at) and priority.
# tasks/views.py
# ... (imports are the same)
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = [IsAuthenticated]
pagination_class = CustomTaskPagination
filter_backends = [
DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter, # Add OrderingFilter
]
filterset_fields = ['completed', 'priority']
search_fields = ['title', 'description']
ordering_fields = ['created_at', 'priority'] # Define orderable fields
Now you can control the sort order:
http://127.0.0.1:8000/api/tasks/?ordering=priority: Orders by priority (ascending).http://127.0.0.1:8000/api/tasks/?ordering=-created_at: Orders by creation date, newest first (the-indicates descending order).
5. Advanced Filtering with a Custom FilterSet
The methods above cover 80% of use cases. But what if you need more complex filtering, like finding tasks with a priority greater than 'Medium', or tasks created within a specific date range? filterset_fields only does exact matching.
For this, we need to create a custom FilterSet class. This is a more powerful and explicit way to define your filtering logic, and it's a hallmark of a well-structured Django project.
When you define a custom FilterSet with more advanced lookups (like date ranges or number comparisons), the DRF browsable API automatically generates a rich filtering interface for you, making your API much easier to test and explore.
Let's build one for our Task model. The article "How to Build REST APIs with Django REST Framework" provides a concise example of creating and using a custom filter class.
How to Build REST APIs with Django REST Framework
This article demonstrates the pattern for creating a dedicated filters.py file and a custom FilterSet class, then hooking it into a ViewSet.
Focus on the two code blocks under "Pagination and Filtering". The first block, in a file named books/filters.py, shows how to define a custom FilterSet class with lookups like gte (greater than or equal to) and icontains. The second block shows how to use this custom class in the ViewSet by setting the filterset_class attribute.
Your Task: Create a TaskFilter Class
-
Create a new file: In your
tasksapp directory, create a file namedfilters.py. -
Define the
TaskFilterclass: Intasks/filters.py, define your custom filters. We'll create filters for creation date ranges.# tasks/filters.py import django_filters from .models import Task class TaskFilter(django_filters.FilterSet): # We use CharFilter for title to do a case-insensitive partial match title = django_filters.CharFilter(lookup_expr='icontains') # We use DateFilter for date-based lookups created_at_after = django_filters.DateFilter(field_name='created_at', lookup_expr='gte') created_at_before = django_filters.DateFilter(field_name='created_at', lookup_expr='lte') class Meta: model = Task # Define fields for exact matching here fields = ['completed', 'priority'] -
Update
TaskViewSet: Go back totasks/views.py. Removefilterset_fieldsand replace it withfilterset_class.# tasks/views.py from .filters import TaskFilter # Import the new class # ... (other imports) class TaskViewSet(viewsets.ModelViewSet): # ... (queryset, serializer_class, etc. remain the same) filter_backends = [ DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter, ] # Replace filterset_fields with filterset_class filterset_class = TaskFilter search_fields = ['title', 'description'] ordering_fields = ['created_at', 'priority']
Now your API is even more powerful! You can perform queries like:
http://127.0.0.1:8000/api/tasks/?created_at_after=2023-10-26: Get all tasks created on or after today's date.http://127.0.0.1:8000/api/tasks/?title=review: Get all tasks with "review" in the title (case-insensitive).
By using filterset_class, you've created a more maintainable and extensible system for filtering.
Conclusion
Congratulations! You have transformed your simple CRUD API into a flexible and powerful data service. Clients can now paginate, filter, search, and order data, giving them precise control over the information they retrieve. This dramatically reduces wasted bandwidth and processing time on both the client and server.
Key Takeaways:
- DRF's filter backends (
DjangoFilterBackend,SearchFilter,OrderingFilter) provide a declarative way to add query capabilities to your API. - Simple filtering is enabled by installing
django-filterand using thefilterset_fields,search_fields, andordering_fieldsattributes in your view. - For advanced logic like date ranges or numerical comparisons, creating a custom
FilterSetclass and assigning it tofilterset_classis the more powerful and professional approach.
Your API is now functional, secure, paginated, and filterable. But how will other developers know how to use it? In the next lesson, we will address this by learning how to automatically generate interactive API documentation using Swagger/OpenAPI, a crucial step for team collaboration and API adoption.