Welcome back! In this lesson, we'll dive into advanced filtering techniques to make our To-Do API even more powerful and user-friendly. You've already learned the basics of filtering in the previous lesson. Here, we will focus on adding more flexible filtering capabilities using the django-filter
library, which makes complex queries easier to handle. By the end of this lesson, you will be able to create custom filters to refine your API queries.
Before we get started with advanced filtering, let's briefly recap the initial setup of our Django-based To-Do API. This will ensure we're all on the same page.
Here's the summary code of our current setup:
Python1# project/myapp/models.py 2from django.db import models 3 4class Todo(models.Model): 5 task = models.CharField(max_length=255) 6 completed = models.BooleanField(default=False) 7 priority = models.IntegerField() 8 assignee = models.CharField(max_length=255, blank=True, null=True) 9 group = models.CharField(max_length=255, blank=True, null=True) 10 11 def __str__(self): 12 return self.task 13 14# project/myapp/serializers.py 15from rest_framework import serializers 16from .models import Todo 17 18class TodoSerializer(serializers.ModelSerializer): 19 class Meta: 20 model = Todo 21 fields = '__all__'
This setup provides a model we will work with. Now, let's take it a step further by adding powerful filtering options.
The django-filter
library allows you to create advanced custom filters. Filters are defined in a FilterSet
class that specifies which fields are available for filtering and how filtering should be performed. We will create a separate filters.py
file to store filters. Inside, we'll define a TodoFilter
class to filter To-Do items based on various fields. This class should be inherited from filters.FilterSet
, as follows:
Python1# project/myapp/filters.py 2from django_filters import rest_framework as filters 3from .models import Todo 4 5class TodoFilter(filters.FilterSet): 6 pass
A common filter is one that allows you to search for a field containing specific text. For example, to filter tasks based on a substring match in their names, use the CharFilter
with lookup_expr
set to icontains
:
Python1task = filters.CharFilter(lookup_expr='icontains')
Here, we create a filter for the task
field, allowing case-insensitive, partial matches using icontains
. This means users can find To-Do items with tasks that include specific text. For other cases, django-filters
provide a comprehensive set of options for lookup_expr
:
exact
: Matches exactly.iexact
: Case-insensitive exact match.contains
: Contains substring.icontains
: Case-insensitive contains substring.startswith
: Starts with substring.istartswith
: Case-insensitive starts with substring.endswith
: Ends with substring.iendswith
: Case-insensitive ends with substring.
A Boolean filter is used for binary fields, such as completed status. You can use BooleanFilter
:
Python1completed = filters.BooleanFilter()
This filter allows users to query tasks based on their completion status. There is nothing special about it, but we should include it to ensure our custom filter has the complete required functionality.
Filters can also be used for numeric fields, such as priority:
Python1priority = filters.NumberFilter()
This allows exact matches against the specified numeric value.
The completed
and priority
filters do not specify a field_name
. In such cases, django-filter
defaults to using the filter name ('completed'
and 'priority'
) as the field name. The default value for lookup_expr
is 'exact'
, meaning it performs an exact match unless specified otherwise.
For comparing numerical values, you can create filters using lookup expressions like lt
(less than) and gt
(greater than):
Python1priority__lt = filters.NumberFilter(field_name='priority', lookup_expr='lt') 2priority__gt = filters.NumberFilter(field_name='priority', lookup_expr='gt')
These filters allow users to find tasks with priority less than or greater than a specified value.
Here are other options you can use for lookup_expr
in a similar manner:
exact
: Matches exactly.gt
: Greater than.gte
: Greater than or equal.lt
: Less than.lte
: Less than or equal.
django-filter
also provides filters for date and time fields. You can filter To-Do items based on creation or due date using DateFilter
or DateTimeFilter
. Here is an example:
Imagine we added a due_date
field to our model:
Python1from .models import Todo 2 3class TodoFilter(filters.FilterSet): 4 due_date = filters.DateFilter()
You can specify different lookup_expr
values for date and datetime fields, just as with other filters:
exact
: Matches the exact date/time.gt
: Greater than the specified date/time.gte
: Greater than or equal to the specified date/time.lt
: Less than the specified date/time.lte
: Less than or equal to the specified date/time.date
: Extracts the date part of the datetime.year
: Extracts the year part of the datetime.month
: Extracts the month part of the datetime.day
: Extracts the day part of the datetime.hour
: Extracts the hour part of the datetime.minute
: Extracts the minute part of the datetime.second
: Extracts the second part of the datetime.week_day
: Extracts the week day part of the datetime.
For instance, to filter tasks due after a specific date:
Python1due_date__gt = filters.DateFilter(field_name='due_date', lookup_expr='gt')
When sending a request to filter on a DateTimeField
, dates and times should be formatted in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ
.
Finally, we must connect the FilterSet
with the model and specify available fields using a Meta
class:
Python1class Meta: 2 model = Todo 3 fields = ['task', 'completed', 'priority', 'priority__lt', 'priority__gt']
The Meta class connects the TodoFilter
with the Todo
model and specifies the fields available for filtering.
Here is the full content of the filters.py
file:
Python1# project/myapp/filters.py 2from django_filters import rest_framework as filters 3from .models import Todo 4 5class TodoFilter(filters.FilterSet): 6 task = filters.CharFilter(lookup_expr='icontains') 7 completed = filters.BooleanFilter() 8 priority = filters.NumberFilter() 9 priority__lt = filters.NumberFilter(field_name='priority', lookup_expr='lt') 10 priority__gt = filters.NumberFilter(field_name='priority', lookup_expr='gt') 11 12 class Meta: 13 model = Todo 14 fields = ['task', 'completed', 'priority', 'priority__lt', 'priority__gt']
Next, we need to integrate our custom filters with the Django views. We'll update the TodoListCreate
view to use our TodoFilter
.
Python1# project/myapp/views.py 2from rest_framework import generics 3from .models import Todo 4from .serializers import TodoSerializer 5from .filters import TodoFilter 6from django_filters.rest_framework import DjangoFilterBackend 7 8class TodoListCreate(generics.ListCreateAPIView): 9 queryset = Todo.objects.all() 10 serializer_class = TodoSerializer 11 filter_backends = [DjangoFilterBackend] 12 filterset_class = TodoFilter
By adding DjangoFilterBackend
to filter_backends
and specifying TodoFilter
as the filterset_class
, we enable advanced filtering for our To-Do list view.
To test our filters, we can use the following examples in send_request.py
:
- Filter where task contains "Task" and completed is True:
Python1response = requests.get(base_url, params={'task': 'Task', 'completed': True})
- Filter where priority is less than 3:
Python1response = requests.get(base_url, params={'priority__lt': 3})
- Filter where priority is greater than 1:
Python1response = requests.get(base_url, params={'priority__gt': 1})
These examples illustrate how to send HTTP GET requests with query parameters and verify the filtered results.
Also let's look at some examples with calling datetime filters, as they can be quite specific:
- Filter where due_date is in October 2023:
Python1response = requests.get(base_url, params={'due_date__month': 10, 'due_date__year': 2023})
- Filter where due_date falls on a Tuesday:
Python1response = requests.get(base_url, params={'due_date__week_day': 2})
Week day 2
means Tuesday. 1
is Monday, 2
is Tuesday, 3
is Wednesday, etc.
In this lesson, we've extended our To-Do API by adding sophisticated filtering capabilities. You now know how to create custom filters using the django-filter
library and integrate them into your Django views. These skills are essential for building flexible and user-friendly APIs.
Congratulations on completing this lesson and, indeed, the entire course! Your dedication has brought you here, and now you're well-equipped to build advanced Django-based APIs. Keep practicing with the exercises that follow and continue honing your skills. Great job!