How to filter querysets using django-filter package
Tags: webdev, django, search, python
Published: 2022-02-16 15:21:57

While recently working on a personal Django project, I applied a search filter functionality by using the django-filter package. Django filter is a popular package that filters a queryset and displays the search results based on the model’s attributes. 

In this article, I will guide you on how to apply the filter functionality in your project. To explain how the package works, I will create a basic Django web project with a model that will apply our filter. Follow up on each step carefully.

## 1. **Setup Working Environment and Create Django Project**
Firstly, you have to create a directory for your project and get inside the directory. The directory is where your project and your environment will be in.

```
$ mkdir myproject 
$ cd myproject
```
Next, create a virtual environment (venv) to store the project's dependencies using the **py -m venv yourenvname** command.
```
$ py -m venv yourenvname
```
You have to activate the virtual environment. You'll need to activate the venv each time you open the command prompt.

```
$ yourenvname\scripts\activate.bat
```
Inside the venv, you then install django.

```
$ pip install django
```
Let's create the project in our directory using django we've just installed.

```
$ django-admin startproject myproject
```
Move in to the project directory you have just created to run the server

```
$ cd myproject
```
Run the server using the command below and then visit this url [http://127.0.0.1:8000/](http://127.0.0.1:8000/). You should see a Django Welcome Page to show you that the project was created successfully.

```
$ py manage.py runserver
```

## 2. Create an app for the project

Now let's create an app for the project. In the command line, stop the server by pressing **Control+c**. Then write the command below.

```
$ py manage.py startapp myapp
```
After creating the app, you have to add the app to the "**INSTALLED_APPS**" list in the **myproject/settings.py** file.

```
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'myapp',
]
```

## 3. Create the Model
Let's create the model that we will use to implement the filter functionality. Name the model **Order** and create it in the file **myapp/models.py**.

```
from django.db import models

class Order(models.Model):
    STATUS = (
        ('Pending', 'Pending'),
        ('Out for delivery', 'Out for delivery'),
        ('Delivered', 'Delivered'),
    )
    CATEGORY = (
        ('Indoor', 'Indoor'),
        ('Outdoor', 'Outdoor'),
    )
    product = models.CharField(max_length=100, null=True)
    category = models.CharField(max_length=100, null=True, 
    choices=CATEGORY)
    status = models.CharField(max_length=100, null=True, 
    choices=STATUS)
    date_created = models.DateTimeField(auto_now_add=True, 
    null=True)

    def __str__(self):
        return self.product
```
Using the following commands, run the migrations to create a database based on the model above.

```
$ py manage.py makemigrations
$ py manage.py migrate
```
Register the model in the **myapp/admin.py** file.

```
from django.contrib import admin
from .models import Order

# Register your models here.

admin.site.register(Order)
```

## 3. Define the views.
Let's create a view function in the **myapp/views.py** file. We have to import the Order model from models.py file. The function will retrieve the order objects from the Order model. 

```
from .models import Order

def index(request):
    orders = Order.objects.all().order_by('-date_created')
    context = {
            'orders': orders,
    }
    return render(request, 'index.html', context)

```

## 4. Create the url paths
Now we need to configure our url paths. Within the app folder, create a new **urls.py** file.
_myapp/urls.py_ 

```
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index)
]
```
Projects with multiple apps are common. The apps need their own dedicated url paths. We then update the main project's **myproject/urls.py** file to include the app's urls.

```
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('myapp.urls')),
    path('admin/', admin.site.urls),
]

```

## 4. Create the Templates
In the root folder, let's create a directory for our project's _HTML_ template file called **index.html**. The filter form will be in this template.

```
$ mkdir templates
$ touch templates/index.html
```
Next, we must configure the **myproject/settings.py** file to connect Django to our new template directory. Scroll through the file and change the settings in _TEMPLATES_.

```
import os
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], #new
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
```
Write the following code in the _HTML_ file.

```
        <h5>ORDERS:</h5>
        <br>
        <div class="card card-body ml-3 mr-3">
            <table class="table table-sm">
                <tr>
                    <th>Product</th>        
                    <th>Category</th>
                    <th>Status</th>
                    <th>Date Ordered</th>
                </tr>
                {% for order in orders %}
                <tr>
                    <td>{{ order.product }}</td>
                    <td>{{ order.category }}</td>
                    <td>{{ order.status }}</td>
                    <td>{{ order.date_created}}</td>
                </tr>
                {% endfor %}
            </table>
        </div>
 
```
Let's create a superuser who will login to the admin page and add data that will be rendered in the template. 

```
$ python manage.py createsuperuser
```
The html template will display data added by the superuser  in the admin page.


![Orders Table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/71xvvxxmy8ictu07xhcu.JPG)

## 5. Installing django-filter to Filter Querysets
Let's install the django-filter package by running the following command in your shell or command prompt.

```
$ pip install django-filter
```
Add the django-filter package in the **INSTALLED_APPS** list in the **myproject/settings.py** just like we added the project's app before.

```
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'myapp',
    'django_filters',
]
```
Next, we create a **filters.py** file in the app directory. The filter will be based on the **Order** model. We will import the following filter arguments.

- **CharFilter** matches the character values passed in the CharField or TextField

- **ChoiceFilter** matches the values that are passed in the _choices_ argument in the model.

- **DateTimeFromToRangeWidget** filters values based on the range between two dates. 

We are filtering the orders based on the following attributes:

- product

- category

- status

- date_created

```
import django_filters
from django_filters import DateTimeFromToRangeFilter, ChoiceFilter, CharFilter

from .models import Order

STATUS = (
    ('Pending', 'Pending'),
    ('Out for delivery', 'Out for delivery'),
    ('Delivered', 'Delivered'),
)

CATEGORY = (
    ('Indoor', 'Indoor'),
    ('Outdoor', 'Outdoor'),
)


class OrderFilter(django_filters.FilterSet):
    product = CharFilter(field_name = 'product', 
              lookup_expr='icontains')
    status = ChoiceFilter(choices=STATUS)
    category = ChoiceFilter(choices=CATEGORY)
    date_created = DateTimeFromToRangeFilter(
                  widget=django_filters.widgets.RangeWidget(
                  attrs={'type': 'date'}
    ))

    class Meta:
        model = Order
        fields = [
            'product',
            'category',
            'status',
            'date_created'
        ]
```
You will create a filter form by inheriting from  **django_filters.Filterset** similar to creating Model Forms. 

CharFilter will filter through the products using the **lookup_expr='icontains'** argument and display the results that match the characters entered in the form. ChoiceFilter will attribute to filtering by choices in the "category" and "status" fields, while DateTimeFromToRangeFilter will display the search results from filtering the orders by "date_created". 

Define the logic for the OrderFilter form inside the **myapp/views.py** and then render it in the template. 
We then carryout the following steps in the view:

1. Import the OrderFilter from _filters.py_.
 
2. Add a GET request and the orders queryset we are filtering, to the variable **myFilter**. 

3. Rename the variable myFilter to **orders** queryset.

```
from .filters import OrderFilter

def index(request):
    orders = Order.objects.all().order_by('-date_created')
    myFilter = OrderFilter(request.GET, queryset=orders)
    orders = myFilter.qs
    context = {
        'orders': orders,
        'myFilter': myFilter,
    }
    return render(request, 'index.html', context)
```

Copy and paste the following code to create a form in _index.html_ that will render the filter form. Note that the _form method="GET"_ because you are sending a GET request for your search queries.

```
<div class="row">
    <div class="col">
        <div class="card card-body ml-3 mr-3">
            <form method="GET" action="" class="form-inline">
                {% csrf_token %}
                    <div>
                    {{ myFilter.form }}
                    <div>
                        <button type="submit" class="btn btn- 
                        secondary">SEARCH</button>
                    </div>
            </form>
        </div>
    </div>
</div>
```
Rerun the server and search your queries based on the filter form.  


![FilterForm](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/92rr6crj7z4akq8a62n8.JPG)
Search for orders based on your filters of choice. 


![Search Results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pvp0ufk0mzatlazf50kh.JPG)

The table will now display your search queries.

I hope you find this article helpful to your coding journey. Please share among your coder friends. Incase of any questions, let's chat in the Comments Section. Happy coding.