Welcome back! In the previous lesson, we added the ability to sort todos in our ToDo application. This lesson focuses on enhancing our application further by adding the ability to filter todos.
Filtering is an essential feature in any web application, as it improves user experience by making data easier to navigate and find. Today, we will learn how to implement this functionality step-by-step.
Before diving deeper, let's briefly remind ourselves of what filtering is.
Filtering involves displaying only the data that matches certain criteria, effectively hiding the rest. For example, filtering todos by title initial characters allows users to narrow down the list to items that start with specific letters.
In this lesson, we will implement filtering todos based on the initial letters of their titles.
Let’s start by updating the get_all
method in app/services/todo_service.py
to support filtering.
Here’s the updated method:
Python1from models.todo import Todo, db 2 3class TodoService: 4 @staticmethod 5 def get_all(sort_by=None, filter_by=None): 6 # Initialize a query on the Todo model 7 query = Todo.query 8 # Apply filtering if filter_by is provided 9 if filter_by: 10 query = query.filter(Todo.title.ilike(f'{filter_by}%')) 11 # Apply sorting if sort_by is 'title' 12 if sort_by == 'title': 13 query = query.order_by(Todo.title) 14 # Execute the query and return all matching todos 15 return query.all()
Let's break this down:
- Initial Query:
- We start by initializing a query on the
Todo
model:query = Todo.query
. This sets up a base query to build on.
- We start by initializing a query on the
- Filtering:
- If
filter_by
is provided, we filter the query:query = query.filter(Todo.title.ilike(f'{filter_by}%'))
. Let's break down what this does: ilike
: This is a method that performs a case-insensitive match. It checks if the data inTodo.title
starts with the specified letters (given infilter_by
) without worrying about uppercase or lowercase differences.%
: This is a wildcard character used in SQL that matches any sequence of characters. In the context of'{filter_by}%'
, it ensures that any additional characters afterfilter_by
are included. For example, iffilter_by
is "A", it will match titles like "Apple", "Ascend", etc.
- If
- Sorting:
- If
sort_by
is set to 'title', we sort the results:query = query.order_by(Todo.title)
. This arranges the todos alphanumerically by their titles.
- If
- Fetching Results:
- Finally,
return query.all()
executes the query and returns all matching todos.
- Finally,
The order of operations is important here: filtering is applied first, and then sorting. This ensures that we can perform both actions together or each individually based on the provided parameters, making our service layer highly flexible for various user needs.
Now, we’ll modify app/controllers/todo_controller.py
to capture the filter parameter from the URL and pass it to our updated service method.
Here’s the updated controller:
Python1from flask import Blueprint, render_template, request, redirect, url_for 2from services.todo_service import TodoService 3 4todo_service = TodoService() 5todo_controller = Blueprint('todo', __name__) 6 7@todo_controller.route('/', methods=['GET']) 8def list_todos(): 9 # Capture sort and filter parameters from the URL 10 sort_by = request.args.get('sort_by') 11 filter_by = request.args.get('filter_by') 12 # Fetch sorted and filtered todos 13 todos = todo_service.get_all(sort_by=sort_by, filter_by=filter_by) 14 # Render the todo list template with the todos 15 return render_template('todo_list.html', todos=todos)
Let's break this down:
-
Capturing Parameters:
- We capture the
sort_by
andfilter_by
parameters from the URL query string usingsort_by = request.args.get('sort_by')
andfilter_by = request.args.get('filter_by')
. This allows us to dynamically read these values whenever a GET request is made to the root route (/
). These parameters can beNone
, meaning that if no sort or filter criteria are provided, all todos will be returned.
- We capture the
-
Fetching Filtered Data:
- We call
todo_service.get_all(sort_by=sort_by, filter_by=filter_by)
with the captured parameters. This fetches the todos based on the sort and filter criteria specified by the user. If the parameters areNone
, the method will return all todos.
- We call
-
Rendering the Template:
- We render the
todo_list.html
template with the fetched todos usingreturn render_template('todo_list.html', todos=todos)
. This passes the filtered and possibly sorted list of todos to the template for display.
- We render the
The final step is to modify app/templates/todo_list.html
to add filter options in the form, enabling users to input their preferences.
Here’s the updated template:
HTML, XML1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Todo List</title> 7</head> 8<body> 9 <h1>Todo List</h1> 10 11 <!-- Form for filtering and sorting todos --> 12 <form action="{{ url_for('todo.list_todos') }}" method="GET"> 13 <div class="form-group"> 14 <label for="sort_by">Sort by:</label> 15 <select id="sort_by" name="sort_by"> 16 <option value="" selected>None</option> 17 <option value="title">Title</option> 18 </select> 19 </div> 20 <div class="form-group"> 21 <label for="filter_by">Filter by initial letters:</label> 22 <input type="text" id="filter_by" name="filter_by" placeholder="Enter letters..."> 23 </div> 24 <button type="submit">Apply</button> 25 </form> 26 27 <!-- List all todos... --> 28 29 <!-- Form for adding a new todo... --> 30 31</body> 32</html>
The key addition here is the text input field to filter todos by the initial letters of their titles. If no sorting option or filter is selected, the submit button will send None
to both parameters, resulting in all todos being returned. These enhancements enable users to specify their sorting and filtering preferences, while still allowing for all items to be displayed if no specific options are chosen.
Now that we have implemented filtering, it’s essential to test the functionality.
-
Filtering:
- Enter a letter or a sequence of letters in the filter input and submit the form.
- The URL for filtering by initial letters "A" would be:
/?filter_by=A
- The items whose titles start with the entered letters should be displayed.
-
Sorting and Filtering Together:
- Use the sort dropdown to select "Title" and enter a letter or a sequence of letters in the filter input, then submit the form.
- The URL for both sorting by title and filtering by initial letters "A" would be:
/?sort_by=title&filter_by=A
- The items should be sorted alphabetically by title and filtered by the entered letters.
-
Returning All Todos:
- Submit the form without selecting a sort option or entering filter criteria.
- The URL for returning all todos would be:
/
- All todos should be displayed without any sorting or filtering applied.
In this lesson, we enhanced our ToDo application by implementing filtering functionality. We:
- Updated the service layer to support dynamic filtering.
- Modified the controller to capture user inputs for filtering.
- Enhanced the template to allow users to input their filtering preferences.
- Provided strategies to test the new functionality.
These improvements make our application more robust and user-friendly. Now it’s time for you to practice these concepts. Head over to the practice exercises to solidify your understanding and gain hands-on experience with implementing filtering.