Lesson 2
Advanced Filtering in Django API
Introduction to Advanced Filtering

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.

Recap of Initial Setup

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:

Python
1# 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.

Creating Custom Filters

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:

Python
1# project/myapp/filters.py 2from django_filters import rest_framework as filters 3from .models import Todo 4 5class TodoFilter(filters.FilterSet): 6 pass
Advanced Filter: Strings

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:

Python
1task = 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.
Advanced Filter: Boolean and Number

A Boolean filter is used for binary fields, such as completed status. You can use BooleanFilter:

Python
1completed = 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:

Python
1priority = 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.

Advanced Filter: Less and Greater Than Filters

For comparing numerical values, you can create filters using lookup expressions like lt (less than) and gt (greater than):

Python
1priority__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.
Advanced Filter: Date and Time

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:

Python
1from .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:

Python
1due_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.

Advanced Filter: Meta Class

Finally, we must connect the FilterSet with the model and specify available fields using a Meta class:

Python
1class 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.

Complete FilterSet Example

Here is the full content of the filters.py file:

Python
1# 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']
Integrating Filters with Django Views

Next, we need to integrate our custom filters with the Django views. We'll update the TodoListCreate view to use our TodoFilter.

Python
1# 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.

Testing Our Filtering API

To test our filters, we can use the following examples in send_request.py:

  1. Filter where task contains "Task" and completed is True:
Python
1response = requests.get(base_url, params={'task': 'Task', 'completed': True})
  1. Filter where priority is less than 3:
Python
1response = requests.get(base_url, params={'priority__lt': 3})
  1. Filter where priority is greater than 1:
Python
1response = 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:

  1. Filter where due_date is in October 2023:
Python
1response = requests.get(base_url, params={'due_date__month': 10, 'due_date__year': 2023})
  1. Filter where due_date falls on a Tuesday:
Python
1response = 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.

Summary and Next Steps

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!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.