Lesson 4
Filtering Data Using Initial Characters
Filtering Data Using Initial Characters

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.

What is Filtering?

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.

Implementing Filtering in the Service Layer

Let’s start by updating the get_all method in app/services/todo_service.py to support filtering.

Here’s the updated method:

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

  1. Initial Query:
    • We start by initializing a query on the Todo model: query = Todo.query. This sets up a base query to build on.
  2. 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 in Todo.title starts with the specified letters (given in filter_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 after filter_by are included. For example, if filter_by is "A", it will match titles like "Apple", "Ascend", etc.
  3. 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.
  4. Fetching Results:
    • Finally, return query.all() executes the query and returns all matching todos.

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.

Updating the Controller to Handle Filtering

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:

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

  1. Capturing Parameters:

    • We capture the sort_by and filter_by parameters from the URL query string using sort_by = request.args.get('sort_by') and filter_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 be None, meaning that if no sort or filter criteria are provided, all todos will be returned.
  2. 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 are None, the method will return all todos.
  3. Rendering the Template:

    • We render the todo_list.html template with the fetched todos using return render_template('todo_list.html', todos=todos). This passes the filtered and possibly sorted list of todos to the template for display.
Modifying the Template for User Input

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, XML
1<!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.

Filtering in Practice

Now that we have implemented filtering, it’s essential to test the functionality.

  1. 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.
  2. 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.
  3. 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.
Summary and Preparation for Practice

In this lesson, we enhanced our ToDo application by implementing filtering functionality. We:

  1. Updated the service layer to support dynamic filtering.
  2. Modified the controller to capture user inputs for filtering.
  3. Enhanced the template to allow users to input their filtering preferences.
  4. 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.

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